#include "CrashReportApp.h" #include #include #include #include #include #include #include #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 #include #include "client/windows/sender/crash_report_sender.h" namespace { std::wstring ToPlatformString(const std::string& utf8) { return std::wstring_convert, std::wstring::traits_type::char_type>().from_bytes(utf8); } bool SendMinidump(const std::string& url, const wxString& minidumpPath, const std::map& arguments, const wxString& commentsFilePath) { std::map files; files[L"upload_file_minidump"] = minidumpPath.wc_str(); if (!commentsFilePath.empty()) { files[wxFileName(commentsFilePath).GetFullName().wc_str()] = commentsFilePath.wc_str(); } std::map 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& arguments, const wxString& commentsFilePath) { std::map 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 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 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::max()); google_breakpad::MinidumpMemoryList::set_max_regions(std::numeric_limits::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 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& 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 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; }); } }