mirror of
https://github.com/cookiengineer/audacity
synced 2025-10-20 09:31:15 +02:00
Crashreporting
This commit is contained in:
155
crashreports/internal/unix/CrashReportContext.cpp
Normal file
155
crashreports/internal/unix/CrashReportContext.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
/*!********************************************************************
|
||||
*
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
CrashReportContext.cpp
|
||||
|
||||
Vitaly Sverchinsky
|
||||
|
||||
Some parts of the code are designed to operate while app is crashing,
|
||||
so there may be some restrictions on heap usage. For more information
|
||||
please read Breakpad documentation.
|
||||
|
||||
**********************************************************************/
|
||||
#include "CrashReportContext.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include "client/mac/handler/exception_handler.h"
|
||||
#else
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#endif
|
||||
|
||||
bool SendReport(CrashReportContext* c, const char* minidumpPath)
|
||||
{
|
||||
auto pid = fork();
|
||||
if(pid == 0)
|
||||
{
|
||||
if(c->mParameters[0] != 0)
|
||||
{
|
||||
execl(c->mSenderPath, CRASHREPORTER_PROGRAM_NAME, "-a", c->mParameters, "-u", c->mReportURL, minidumpPath, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
execl(c->mSenderPath, CRASHREPORTER_PROGRAM_NAME, "-u", c->mReportURL, minidumpPath, NULL);
|
||||
}
|
||||
fprintf(stderr, "Failed to start handler: %s\n", strerror(errno));
|
||||
abort();
|
||||
}
|
||||
return pid != -1;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
//converts parameters map to a string, so that the Crash Reporting program
|
||||
//would understand them
|
||||
std::string StringifyParameters(const std::map<std::string, std::string>& parameters)
|
||||
{
|
||||
std::stringstream stream;
|
||||
|
||||
std::size_t parameterIndex = 0;
|
||||
std::size_t parametersCount = parameters.size();
|
||||
for (auto& pair : parameters)
|
||||
{
|
||||
stream << pair.first.c_str() << "=\"" << pair.second.c_str() << "\"";
|
||||
++parameterIndex;
|
||||
if (parameterIndex < parametersCount)
|
||||
stream << ",";
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
//copy contents of src to a raw char dest buffer,
|
||||
//returns false if dest is not large enough
|
||||
bool StrcpyChecked(char* dest, size_t destsz, const std::string& src)
|
||||
{
|
||||
if(src.length() < destsz)
|
||||
{
|
||||
memcpy(dest, src.c_str(), src.length());
|
||||
dest[src.length()] = '\0';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
static constexpr size_t MaxDumpPathLength{ 4096 };
|
||||
static char DumpPath[MaxDumpPathLength];
|
||||
|
||||
bool DumpCallback(const char* dump_dir, const char* minidump_id, void* context, bool succeeded)
|
||||
{
|
||||
if(succeeded)
|
||||
{
|
||||
const int PathDumpLength = strlen(dump_dir) + strlen("/") + strlen(minidump_id) + strlen(".dmp");
|
||||
if(PathDumpLength < MaxDumpPathLength)
|
||||
{
|
||||
strcpy(DumpPath, dump_dir);
|
||||
strcat(DumpPath, "/");
|
||||
strcat(DumpPath, minidump_id);
|
||||
strcat(DumpPath, ".dmp");
|
||||
auto crashReportContext = static_cast<CrashReportContext*>(context);
|
||||
return SendReport(crashReportContext, DumpPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
#else
|
||||
bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
|
||||
{
|
||||
if(succeeded)
|
||||
{
|
||||
auto crashReportContext = static_cast<CrashReportContext*>(context);
|
||||
return SendReport(crashReportContext, descriptor.path());
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CrashReportContext::SetSenderPathUTF8(const std::string& path)
|
||||
{
|
||||
return StrcpyChecked(mSenderPath, MaxBufferLength, path + "/" + CRASHREPORTER_PROGRAM_NAME);
|
||||
}
|
||||
|
||||
bool CrashReportContext::SetReportURL(const std::string& url)
|
||||
{
|
||||
return StrcpyChecked(mReportURL, MaxBufferLength, url);
|
||||
}
|
||||
|
||||
bool CrashReportContext::SetParameters(const std::map<std::string, std::string>& p)
|
||||
{
|
||||
auto str = StringifyParameters(p);
|
||||
return StrcpyChecked(mParameters, MaxBufferLength, str);
|
||||
}
|
||||
|
||||
void CrashReportContext::StartHandler(const std::string& databasePath)
|
||||
{
|
||||
//intentinal leak: error hooks may be useful while application is terminating
|
||||
//CrashReportContext data should be alive too...
|
||||
#if(__APPLE__)
|
||||
static auto handler = new google_breakpad::ExceptionHandler(
|
||||
databasePath,
|
||||
nullptr,
|
||||
DumpCallback,
|
||||
this,
|
||||
true,
|
||||
nullptr
|
||||
);
|
||||
#else
|
||||
google_breakpad::MinidumpDescriptor descriptor(databasePath);
|
||||
static auto handler = new google_breakpad::ExceptionHandler(
|
||||
descriptor,
|
||||
nullptr,
|
||||
DumpCallback,
|
||||
this,
|
||||
true,
|
||||
-1
|
||||
);
|
||||
#endif
|
||||
}
|
43
crashreports/internal/unix/CrashReportContext.h
Normal file
43
crashreports/internal/unix/CrashReportContext.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*!********************************************************************
|
||||
*
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
CrashReportContext.h
|
||||
|
||||
Vitaly Sverchinsky
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
//!This object is for internal usage.
|
||||
/*! Simple POD type, holds user data required to start handler.
|
||||
* Fields are initialized with Set* methods,
|
||||
* which may return false if internal buffer isn't large enough
|
||||
* to store value passed as an argument.
|
||||
* After initialization call StartHandler providing path to the
|
||||
* database, where minidumps will be stored.
|
||||
*/
|
||||
class CrashReportContext
|
||||
{
|
||||
static constexpr size_t MaxBufferLength{ 2048 };
|
||||
|
||||
char mSenderPath[MaxBufferLength]{};
|
||||
char mReportURL[MaxBufferLength]{};
|
||||
char mParameters[MaxBufferLength]{};
|
||||
|
||||
public:
|
||||
|
||||
bool SetSenderPathUTF8(const std::string& path);
|
||||
bool SetReportURL(const std::string& url);
|
||||
bool SetParameters(const std::map<std::string, std::string>& p);
|
||||
|
||||
void StartHandler(const std::string& databasePath);
|
||||
|
||||
private:
|
||||
//helper function which need access to a private data, but should not be exposed to a public class interface
|
||||
friend bool SendReport(CrashReportContext* ctx, const char* minidumpPath);
|
||||
};
|
165
crashreports/internal/win32/CrashReportContext.cpp
Normal file
165
crashreports/internal/win32/CrashReportContext.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/*!********************************************************************
|
||||
*
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
CrashReportContext.cpp
|
||||
|
||||
Vitaly Sverchinsky
|
||||
|
||||
Some parts of the code are designed to operate while app is crashing,
|
||||
so there may be some restrictions on heap usage. For more information
|
||||
please read Breakpad documentation.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "CrashReportContext.h"
|
||||
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <sstream>
|
||||
#include "client/windows/handler/exception_handler.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
//copy src(null-terminated) to dst,
|
||||
//returns false if dest is not large enough
|
||||
bool StrcpyChecked(wchar_t* dest, size_t destsz, const wchar_t* src)
|
||||
{
|
||||
auto len = wcslen(src);
|
||||
if (len < destsz)
|
||||
{
|
||||
memcpy(dest, src, sizeof(wchar_t) * len);
|
||||
dest[len] = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//appends src(null-terminated) to dest, destsz is the total dest buffer size, not remaining
|
||||
//returns false if dest is not large enough
|
||||
bool StrcatChecked(wchar_t* dest, size_t destsz, const wchar_t* src)
|
||||
{
|
||||
auto srclen = wcslen(src);
|
||||
auto dstlen = wcslen(dest);
|
||||
if (srclen + dstlen < destsz)
|
||||
{
|
||||
memcpy(dest + dstlen, src, sizeof(wchar_t) * srclen);
|
||||
dest[srclen + dstlen] = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//converts parameters map to a string, so that the Crash Reporting program
|
||||
//would understand them
|
||||
std::string StringifyParameters(const std::map<std::string, std::string>& parameters)
|
||||
{
|
||||
std::stringstream stream;
|
||||
|
||||
std::size_t parameterIndex = 0;
|
||||
std::size_t parametersCount = parameters.size();
|
||||
for (auto& pair : parameters)
|
||||
{
|
||||
stream << pair.first.c_str() << "=\\\"" << pair.second.c_str() << "\\\"";
|
||||
++parameterIndex;
|
||||
if (parameterIndex < parametersCount)
|
||||
stream << ",";
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
}
|
||||
|
||||
bool MakeCommand(CrashReportContext* c, const wchar_t* path, const wchar_t* id)
|
||||
{
|
||||
//utility path
|
||||
auto ok = StrcpyChecked(c->mCommand, CrashReportContext::MaxCommandLength, L"\"");
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, c->mSenderPath);
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, L"\"");
|
||||
|
||||
//parameters: /p "..."
|
||||
if (ok && c->mParameters[0] != 0)
|
||||
{
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, L" /a \"");
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, c->mParameters);
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, L"\"");
|
||||
}
|
||||
//crash report URL: /u https://...
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxBufferLength, L" /u \"");
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxBufferLength, c->mReportURL);
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxBufferLength, L"\" ");
|
||||
//minidump path: path/to/minidump.dmp
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, L" \"");
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, path);
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, L"\\");
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, id);
|
||||
ok = ok && StrcatChecked(c->mCommand, CrashReportContext::MaxCommandLength, L".dmp\"");
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SendReport(CrashReportContext* c, const wchar_t* path, const wchar_t* id)
|
||||
{
|
||||
if (!MakeCommand(c, path, id))
|
||||
return false;
|
||||
|
||||
STARTUPINFOW si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_SHOW;
|
||||
|
||||
PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
if (CreateProcessW(NULL, c->mCommand, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
|
||||
{
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UploadReport(
|
||||
const wchar_t* dump_path,
|
||||
const wchar_t* minidump_id,
|
||||
void* context,
|
||||
EXCEPTION_POINTERS* /*exinfo*/,
|
||||
MDRawAssertionInfo* /*assertion*/,
|
||||
bool succeeded)
|
||||
{
|
||||
CrashReportContext* crashReportContext = static_cast<CrashReportContext*>(context);
|
||||
if (!SendReport(crashReportContext, dump_path, minidump_id))
|
||||
return false;
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
bool CrashReportContext::SetSenderPathUTF8(const std::string& path)
|
||||
{
|
||||
auto fullpath = path + "\\" + CRASHREPORTER_PROGRAM_NAME;
|
||||
return StrcpyChecked(mSenderPath, MaxBufferLength, std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(fullpath).c_str());
|
||||
}
|
||||
|
||||
bool CrashReportContext::SetReportURL(const std::string& url)
|
||||
{
|
||||
return StrcpyChecked(mReportURL, MaxBufferLength, std::wstring(url.begin(), url.end()).c_str());
|
||||
}
|
||||
|
||||
bool CrashReportContext::SetParameters(const std::map<std::string, std::string>& p)
|
||||
{
|
||||
auto str = StringifyParameters(p);
|
||||
return StrcpyChecked(mParameters, MaxBufferLength, std::wstring(str.begin(), str.end()).c_str());
|
||||
}
|
||||
|
||||
void CrashReportContext::StartHandler(const std::string& databasePath)
|
||||
{
|
||||
//intentinal leak: error hooks may be useful while application is terminating
|
||||
//CrashReportContext data should be alive too...
|
||||
static auto handler = new google_breakpad::ExceptionHandler(
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(databasePath),
|
||||
NULL,
|
||||
UploadReport,
|
||||
this,
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL);
|
||||
}
|
||||
|
47
crashreports/internal/win32/CrashReportContext.h
Normal file
47
crashreports/internal/win32/CrashReportContext.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*!********************************************************************
|
||||
*
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
CrashReportContext.h
|
||||
|
||||
Vitaly Sverchinsky
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
//!This object is for internal usage.
|
||||
/*! Simple POD type, holds user data required to start handler.
|
||||
* Fields are initialized with Set* methods,
|
||||
* which may return false if internal buffer isn't large enough
|
||||
* to store value passed as an argument.
|
||||
* After initialization call StartHandler providing path to the
|
||||
* database, where minidumps will be stored.
|
||||
*/
|
||||
class CrashReportContext final
|
||||
{
|
||||
static constexpr size_t MaxBufferLength{ 2048 };
|
||||
static constexpr size_t MaxCommandLength{ 8192 };
|
||||
|
||||
wchar_t mSenderPath[MaxBufferLength]{};
|
||||
wchar_t mReportURL[MaxBufferLength]{};
|
||||
wchar_t mParameters[MaxBufferLength]{};
|
||||
|
||||
//this is a buffer where the command will be built at runtime
|
||||
wchar_t mCommand[MaxCommandLength]{};
|
||||
|
||||
public:
|
||||
bool SetSenderPathUTF8(const std::string& path);
|
||||
bool SetReportURL(const std::string& path);
|
||||
bool SetParameters(const std::map<std::string, std::string>& p);
|
||||
|
||||
void StartHandler(const std::string& databasePath);
|
||||
|
||||
private:
|
||||
//helper functions which need access to a private data, but should not be exposed to a public class interface
|
||||
friend bool MakeCommand(CrashReportContext* ctx, const wchar_t* path, const wchar_t* id);
|
||||
friend bool SendReport(CrashReportContext* ctx, const wchar_t* path, const wchar_t* id);
|
||||
};
|
Reference in New Issue
Block a user