mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-09-24 06:36:08 +00:00
Refactor crash/bug reporter system
* The UI dialog is now in Qt. We run qrenderdoc.exe with a very minimal startup to display the dialog and send the report. * The flow has been simplified to have less text and an easier time to just click through and send. * On the first report, the user is gently nudged to enter their email address for contact and by default the email is saved for next time. They're not nagged more than once about this. * Optionally the user can select to upload the capture. This is always default off, and there is a confirmation dialog making sure the user intended to select it. * After the bug is reported, a unique URL is generated and returned which the user can then click back on to see if there's any update. By default the UI will also remember the URL and check it every couple of days and alert the user in the help menu that there's an update.
This commit is contained in:
@@ -67,6 +67,12 @@ static std::wstring conv(const std::string &str)
|
||||
HINSTANCE hInstance = NULL;
|
||||
|
||||
#if defined(RELEASE)
|
||||
#define CRASH_HANDLER 1
|
||||
#else
|
||||
#define CRASH_HANDLER 0
|
||||
#endif
|
||||
|
||||
#if CRASH_HANDLER
|
||||
// breakpad
|
||||
#include "breakpad/client/windows/crash_generation/client_info.h"
|
||||
#include "breakpad/client/windows/crash_generation/crash_generation_server.h"
|
||||
@@ -77,150 +83,15 @@ using google_breakpad::CrashGenerationServer;
|
||||
|
||||
bool exitServer = false;
|
||||
|
||||
static HINSTANCE CrashHandlerInst = 0;
|
||||
static HWND CrashHandlerWnd = 0;
|
||||
|
||||
bool uploadReport = false;
|
||||
bool uploadDump = false;
|
||||
bool uploadLog = false;
|
||||
string reproSteps = "";
|
||||
|
||||
wstring dump = L"";
|
||||
vector<google_breakpad::CustomInfoEntry> customInfo;
|
||||
wstring logpath = L"";
|
||||
|
||||
INT_PTR CALLBACK CrashHandlerProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch(message)
|
||||
{
|
||||
case WM_INITDIALOG:
|
||||
{
|
||||
HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
|
||||
|
||||
if(hIcon)
|
||||
{
|
||||
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
||||
SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
||||
}
|
||||
|
||||
SetDlgItemTextW(
|
||||
hDlg, IDC_WELCOMETEXT,
|
||||
L"RenderDoc has encountered an unhandled exception or other similar unrecoverable "
|
||||
L"error.\n\n"
|
||||
L"If you had captured but not saved a logfile it should still be available in %TEMP% and "
|
||||
L"will not be deleted,"
|
||||
L"you can try loading it again.\n\n"
|
||||
L"A minidump has been created and the RenderDoc diagnostic log (NOT any capture logfile) "
|
||||
L"is available if you would like "
|
||||
L"to send them back to be analysed. The path for both is found below if you would like "
|
||||
L"to inspect their contents and censor as appropriate.\n\n"
|
||||
L"Neither contains any significant private information, the minidump has some internal "
|
||||
L"states and local memory at the time of the "
|
||||
L"crash & thread stacks, etc. The diagnostic log contains diagnostic messages like "
|
||||
L"warnings and errors.\n\n"
|
||||
L"The only other information sent is the version of RenderDoc, "
|
||||
L"and any notes you include.\n\n"
|
||||
L"Any repro steps or notes would be helpful to include with the report. If you'd like to "
|
||||
L"be contacted about the bug "
|
||||
L"e.g. for updates about its status just include your email & name. Thank you!\n\n"
|
||||
L"Baldur (baldurk@baldurk.org)");
|
||||
|
||||
SetDlgItemTextW(hDlg, IDC_DUMPPATH, dump.c_str());
|
||||
SetDlgItemTextW(hDlg, IDC_LOGPATH, logpath.c_str());
|
||||
|
||||
CheckDlgButton(hDlg, IDC_SENDDUMP, BST_CHECKED);
|
||||
CheckDlgButton(hDlg, IDC_SENDLOG, BST_CHECKED);
|
||||
|
||||
{
|
||||
RECT r;
|
||||
GetClientRect(hDlg, &r);
|
||||
|
||||
int xPos = (GetSystemMetrics(SM_CXSCREEN) - r.right) / 2;
|
||||
int yPos = (GetSystemMetrics(SM_CYSCREEN) - r.bottom) / 2;
|
||||
|
||||
SetWindowPos(hDlg, HWND_TOPMOST, xPos, yPos, 0, 0, SWP_NOSIZE);
|
||||
}
|
||||
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
|
||||
case WM_SHOWWINDOW:
|
||||
{
|
||||
{
|
||||
RECT r;
|
||||
GetClientRect(hDlg, &r);
|
||||
|
||||
int xPos = (GetSystemMetrics(SM_CXSCREEN) - r.right) / 2;
|
||||
int yPos = (GetSystemMetrics(SM_CYSCREEN) - r.bottom) / 2;
|
||||
|
||||
SetWindowPos(hDlg, HWND_NOTOPMOST, xPos, yPos, 0, 0, SWP_NOSIZE);
|
||||
}
|
||||
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
|
||||
case WM_COMMAND:
|
||||
{
|
||||
int ID = LOWORD(wParam);
|
||||
|
||||
if(ID == IDC_DONTSEND)
|
||||
{
|
||||
EndDialog(hDlg, 0);
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
else if(ID == IDC_SEND)
|
||||
{
|
||||
uploadReport = true;
|
||||
uploadDump = (IsDlgButtonChecked(hDlg, IDC_SENDDUMP) != 0);
|
||||
uploadLog = (IsDlgButtonChecked(hDlg, IDC_SENDLOG) != 0);
|
||||
|
||||
char notes[4097] = {0};
|
||||
|
||||
GetDlgItemTextA(hDlg, IDC_NAME, notes, 4096);
|
||||
notes[4096] = 0;
|
||||
|
||||
reproSteps = "Name: ";
|
||||
reproSteps += notes;
|
||||
reproSteps += "\n";
|
||||
|
||||
memset(notes, 0, 4096);
|
||||
GetDlgItemTextA(hDlg, IDC_EMAIL, notes, 4096);
|
||||
notes[4096] = 0;
|
||||
|
||||
reproSteps += "Email: ";
|
||||
reproSteps += notes;
|
||||
reproSteps += "\n\n";
|
||||
|
||||
memset(notes, 0, 4096);
|
||||
GetDlgItemTextA(hDlg, IDC_REPRO, notes, 4096);
|
||||
notes[4096] = 0;
|
||||
|
||||
reproSteps += notes;
|
||||
|
||||
EndDialog(hDlg, 0);
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_QUIT:
|
||||
case WM_DESTROY:
|
||||
case WM_CLOSE:
|
||||
{
|
||||
EndDialog(hDlg, 0);
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (INT_PTR)FALSE;
|
||||
}
|
||||
wstring wdump = L"";
|
||||
std::vector<google_breakpad::CustomInfoEntry> customInfo;
|
||||
|
||||
static void _cdecl OnClientCrashed(void *context, const ClientInfo *client_info,
|
||||
const wstring *dump_path)
|
||||
{
|
||||
if(dump_path)
|
||||
{
|
||||
dump = *dump_path;
|
||||
wdump = *dump_path;
|
||||
|
||||
google_breakpad::CustomClientInfo custom = client_info->GetCustomInfo();
|
||||
|
||||
@@ -498,7 +369,7 @@ struct UpgradeCommand : public Command
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(RELEASE)
|
||||
#if CRASH_HANDLER
|
||||
struct CrashHandlerCommand : public Command
|
||||
{
|
||||
CrashHandlerCommand(const GlobalEnvironment &env) : Command(env) {}
|
||||
@@ -531,22 +402,6 @@ struct CrashHandlerCommand : public Command
|
||||
return 1;
|
||||
}
|
||||
|
||||
CrashHandlerInst = hInstance;
|
||||
|
||||
CrashHandlerWnd =
|
||||
CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 10, 10, NULL, NULL, hInstance, NULL);
|
||||
|
||||
HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
|
||||
|
||||
if(hIcon)
|
||||
{
|
||||
SendMessage(CrashHandlerWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
||||
SendMessage(CrashHandlerWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
||||
}
|
||||
|
||||
ShowWindow(CrashHandlerWnd, SW_HIDE);
|
||||
|
||||
HANDLE readyEvent = CreateEventA(NULL, TRUE, FALSE, "RENDERDOC_CRASHHANDLE");
|
||||
|
||||
if(readyEvent != NULL)
|
||||
@@ -578,11 +433,11 @@ struct CrashHandlerCommand : public Command
|
||||
delete crashServer;
|
||||
crashServer = NULL;
|
||||
|
||||
if(!dump.empty())
|
||||
{
|
||||
logpath = L"";
|
||||
std::wstring wlogpath;
|
||||
|
||||
string report = "";
|
||||
if(!wdump.empty())
|
||||
{
|
||||
string report = "{\n";
|
||||
|
||||
for(size_t i = 0; i < customInfo.size(); i++)
|
||||
{
|
||||
@@ -591,7 +446,7 @@ struct CrashHandlerCommand : public Command
|
||||
|
||||
if(name == L"logpath")
|
||||
{
|
||||
logpath = val;
|
||||
wlogpath = val;
|
||||
}
|
||||
else if(name == L"ptime")
|
||||
{
|
||||
@@ -599,56 +454,77 @@ struct CrashHandlerCommand : public Command
|
||||
}
|
||||
else
|
||||
{
|
||||
report += string(name.begin(), name.end()) + ": " + string(val.begin(), val.end()) + "\n";
|
||||
report += " \"" + string(name.begin(), name.end()) + "\": \"" +
|
||||
string(val.begin(), val.end()) + "\",\n";
|
||||
}
|
||||
}
|
||||
|
||||
DialogBox(CrashHandlerInst, MAKEINTRESOURCE(IDD_CRASH_HANDLER), CrashHandlerWnd,
|
||||
(DLGPROC)CrashHandlerProc);
|
||||
rdcstr reportPath;
|
||||
|
||||
report += "\n\nRepro steps/Notes:\n\n" + reproSteps;
|
||||
RENDERDOC_CreateBugReport(conv(wlogpath).c_str(), conv(wdump).c_str(), reportPath);
|
||||
|
||||
for(size_t i = 0; i < reportPath.size(); i++)
|
||||
if(reportPath[i] == '\\')
|
||||
reportPath[i] = '/';
|
||||
|
||||
report += " \n\"report\": \"" + std::string(reportPath) + "\"\n";
|
||||
report += "}\n";
|
||||
|
||||
if(uploadReport)
|
||||
{
|
||||
mz_zip_archive zip;
|
||||
ZeroMemory(&zip, sizeof(zip));
|
||||
wstring destjson = dumpFolder + L"\\report.json";
|
||||
|
||||
wstring destzip = dumpFolder + L"\\report.zip";
|
||||
FILE *f = NULL;
|
||||
_wfopen_s(&f, destjson.c_str(), L"w");
|
||||
fputs(report.c_str(), f);
|
||||
fclose(f);
|
||||
|
||||
DeleteFileW(destzip.c_str());
|
||||
wchar_t *paramsAlloc = new wchar_t[512];
|
||||
|
||||
mz_zip_writer_init_wfile(&zip, destzip.c_str(), 0);
|
||||
mz_zip_writer_add_mem(&zip, "report.txt", report.c_str(), report.length(),
|
||||
MZ_BEST_COMPRESSION);
|
||||
ZeroMemory(paramsAlloc, sizeof(wchar_t) * 512);
|
||||
|
||||
if(uploadDump && !dump.empty())
|
||||
mz_zip_writer_add_wfile(&zip, "minidump.dmp", dump.c_str(), NULL, 0, MZ_BEST_COMPRESSION);
|
||||
GetModuleFileNameW(NULL, paramsAlloc, 511);
|
||||
|
||||
if(uploadLog && !logpath.empty())
|
||||
mz_zip_writer_add_wfile(&zip, "error.log", logpath.c_str(), NULL, 0, MZ_BEST_COMPRESSION);
|
||||
wchar_t *lastSlash = wcsrchr(paramsAlloc, '\\');
|
||||
|
||||
mz_zip_writer_finalize_archive(&zip);
|
||||
mz_zip_writer_end(&zip);
|
||||
if(lastSlash)
|
||||
*lastSlash = 0;
|
||||
|
||||
int timeout = 10000;
|
||||
wstring body = L"";
|
||||
int code = 0;
|
||||
std::wstring exepath = paramsAlloc;
|
||||
|
||||
std::map<wstring, wstring> params;
|
||||
ZeroMemory(paramsAlloc, sizeof(wchar_t) * 512);
|
||||
|
||||
google_breakpad::HTTPUpload::SendRequest(L"https://renderdoc.org/bugsubmit", params,
|
||||
dumpFolder + L"\\report.zip", L"report", &timeout,
|
||||
&body, &code);
|
||||
_snwprintf_s(paramsAlloc, 511, 511, L"%s/qrenderdoc.exe --crash %s", exepath.c_str(),
|
||||
destjson.c_str());
|
||||
|
||||
DeleteFileW(destzip.c_str());
|
||||
PROCESS_INFORMATION pi;
|
||||
STARTUPINFOW si;
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
|
||||
BOOL success =
|
||||
CreateProcessW(NULL, paramsAlloc, NULL, NULL, FALSE, 0, NULL, exepath.c_str(), &si, &pi);
|
||||
|
||||
if(success && pi.hProcess)
|
||||
{
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
}
|
||||
|
||||
if(pi.hProcess)
|
||||
CloseHandle(pi.hProcess);
|
||||
if(pi.hThread)
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
std::wstring wreport = conv(std::string(report));
|
||||
|
||||
DeleteFileW(wreport.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if(!dump.empty())
|
||||
DeleteFileW(dump.c_str());
|
||||
if(!wdump.empty())
|
||||
DeleteFileW(wdump.c_str());
|
||||
|
||||
if(!logpath.empty())
|
||||
DeleteFileW(logpath.c_str());
|
||||
if(!wlogpath.empty())
|
||||
DeleteFileW(wlogpath.c_str());
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -878,7 +754,7 @@ int WINAPI wWinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrevInstance, _In_
|
||||
// perform an upgrade of the UI
|
||||
add_command("upgrade", new UpgradeCommand(env));
|
||||
|
||||
#if defined(RELEASE)
|
||||
#if CRASH_HANDLER
|
||||
// special WIN32 option for launching the crash handler
|
||||
add_command("crashhandle", new CrashHandlerCommand(env));
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user