Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c4e10d4
WIP
jpnurmi Jul 3, 2025
179fd21
windows
jpnurmi Jul 3, 2025
6cedaee
mac
jpnurmi Jul 3, 2025
bd5c45b
mac: open bundles
jpnurmi Jul 3, 2025
b916097
win: fix member order
jpnurmi Jul 5, 2025
6a7ca4d
linux: minidump
jpnurmi Jul 7, 2025
b37500b
WIP: FeedbackReport
jpnurmi Jul 7, 2025
5ec6840
Clean up DCHECKs
jpnurmi Jul 7, 2025
7ca68a4
Resolve TODO
jpnurmi Jul 7, 2025
87e6602
windows: fix build
jpnurmi Jul 7, 2025
6d17d04
mac: fix build
jpnurmi Jul 7, 2025
b52a9f5
fix pp
jpnurmi Jul 7, 2025
fb00dfb
fix FileOffset format specifier
jpnurmi Jul 7, 2025
43bbde9
/usr/bin/open
jpnurmi Jul 7, 2025
5345609
doc: FeedbackReport
jpnurmi Jul 7, 2025
7b0dd01
doc: LaunchFeedbackHandler
jpnurmi Jul 7, 2025
a5b2fe4
explicit cast because off_t depends on the platform
jpnurmi Jul 7, 2025
f6feefe
fix formatting
jpnurmi Jul 7, 2025
07f2879
argh, you were supposed to write docs
jpnurmi Jul 7, 2025
37b2128
let feedback handler take ownership
jpnurmi Jul 21, 2025
f77635d
Rename to Crash Reporter
jpnurmi Jul 25, 2025
4c20b12
mac: fix missed rename
jpnurmi Jul 28, 2025
af9508c
clean up reports
jpnurmi Jul 29, 2025
3f26be1
allow __sentry-breadcrumbN as attachments
jpnurmi Jul 29, 2025
c78de02
drop path lookup for better security
jpnurmi Aug 5, 2025
4e90921
fix windows build
jpnurmi Aug 5, 2025
cb7008a
Escape attachment filenames in JSON
jpnurmi Aug 5, 2025
dbc4ea5
Unpack & merge __sentry-event and __sentry-breadcrumbsN
jpnurmi Aug 12, 2025
6327ba1
Try [[maybe_unused]]
jpnurmi Aug 12, 2025
f36ae1a
Switch to mpack
jpnurmi Aug 12, 2025
ac6bf7c
Fix linking?
jpnurmi Aug 12, 2025
2dbfe5e
Utilize the JSON lib instead of hand-formatting
jpnurmi Aug 12, 2025
cc400e9
Restore original formatting
jpnurmi Aug 12, 2025
2c0d9a4
Try fix linking again
jpnurmi Aug 12, 2025
3fcf983
Try CXX
jpnurmi Aug 12, 2025
ac8f950
Add missing newlines
jpnurmi Aug 12, 2025
fd36e63
Rename mpack.c to mpack.cc
jpnurmi Aug 12, 2025
79e7077
Drop nlohmann-json in favor of manual JSON formatting
jpnurmi Aug 13, 2025
f7b3970
Minimize & flatten third_party/mpack
jpnurmi Aug 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ add_subdirectory(client)

add_subdirectory(third_party/zlib)
add_subdirectory(third_party/getopt)
add_subdirectory(third_party/mpack)

add_subdirectory(tools)
add_subdirectory(handler)
Expand Down
14 changes: 13 additions & 1 deletion client/client_argv_handling.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ std::vector<std::string> BuildHandlerArgvStrings(
const std::string& http_proxy,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
const std::vector<base::FilePath>& attachments) {
const std::vector<base::FilePath>& attachments,
const base::FilePath& crash_reporter,
const base::FilePath& crash_envelope) {
std::vector<std::string> argv_strings(1, handler.value());

for (const auto& argument : arguments) {
Expand Down Expand Up @@ -69,6 +71,16 @@ std::vector<std::string> BuildHandlerArgvStrings(
FormatArgumentString("attachment", attachment.value()));
}

if (!crash_reporter.empty()) {
argv_strings.push_back(
FormatArgumentString("crash-reporter", crash_reporter.value()));
}

if (!crash_envelope.empty()) {
argv_strings.push_back(
FormatArgumentString("crash-envelope", crash_envelope.value()));
}

return argv_strings;
}

Expand Down
4 changes: 3 additions & 1 deletion client/client_argv_handling.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ std::vector<std::string> BuildHandlerArgvStrings(
const std::string& http_proxy,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
const std::vector<base::FilePath>& attachments = {});
const std::vector<base::FilePath>& attachments = {},
const base::FilePath& crash_reporter = base::FilePath(),
const base::FilePath& crash_envelope = base::FilePath());

//! \brief Flattens a string vector into a const char* vector suitable for use
//! in an exec() call.
Expand Down
271 changes: 271 additions & 0 deletions client/crash_report_database.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
#include <sys/stat.h>

#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "util/file/directory_reader.h"
#include "util/file/file_helper.h"
#include "util/file/filesystem.h"

#include <mpack.h>

namespace crashpad {

namespace {
Expand Down Expand Up @@ -75,6 +79,100 @@ base::FilePath EnsureUniqueFile(const base::FilePath& dir,

return unique;
}

// Escapes a string for JSON (double quotes, backslash, control chars)
std::string EscapeJsonString(const std::string& input) {
std::string output;
output.reserve(input.size() + 8);
for (char c : input) {
switch (c) {
case '\"':
output += "\\\"";
break;
case '\\':
output += "\\\\";
break;
case '\b':
output += "\\b";
break;
case '\f':
output += "\\f";
break;
case '\n':
output += "\\n";
break;
case '\r':
output += "\\r";
break;
case '\t':
output += "\\t";
break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
char buf[7];
snprintf(buf, sizeof(buf), "\\u%04x", c);
output += buf;
} else {
output += c;
}
}
}
return output;
}

std::string MpackToJsonString(mpack_node_t node) {
switch (mpack_node_type(node)) {
case mpack_type_nil:
return "null";
case mpack_type_bool:
return mpack_node_bool(node) ? "true" : "false";
case mpack_type_int:
return std::to_string(mpack_node_i64(node));
case mpack_type_uint:
return std::to_string(mpack_node_u64(node));
case mpack_type_float:
return std::to_string(mpack_node_float(node));
case mpack_type_double:
return std::to_string(mpack_node_double(node));
case mpack_type_str: {
return "\"" +
EscapeJsonString(
std::string(mpack_node_str(node), mpack_node_strlen(node))) +
"\"";
}
case mpack_type_array: {
std::string result = "[";
size_t n = mpack_node_array_length(node);
for (size_t i = 0; i < n; ++i) {
if (i > 0) {
result += ",";
}
result += MpackToJsonString(mpack_node_array_at(node, i));
}
result += "]";
return result;
}
case mpack_type_map: {
std::string result = "{";
size_t n = mpack_node_map_count(node);
for (size_t i = 0; i < n; ++i) {
if (i > 0) {
result += ",";
}
mpack_node_t key = mpack_node_map_key_at(node, i);
mpack_node_t val = mpack_node_map_value_at(node, i);
std::string key_str(mpack_node_str(key), mpack_node_strlen(key));
result += "\"" + key_str + "\":" + MpackToJsonString(val);
}
result += "}";
return result;
}
default:
LOG(ERROR) << "Unsupported mpack node type: " << mpack_node_type(node);
return "";
}
}

} // namespace

CrashReportDatabase::Report::Report()
Expand Down Expand Up @@ -204,6 +302,179 @@ bool CrashReportDatabase::UploadReport::Initialize(const base::FilePath& path,
return reader_->Open(path);
}

bool CrashReportDatabase::Envelope::Initialize(const base::FilePath& path) {
path_ = path;
if (path.empty()) {
return false;
}
writer_ = std::make_unique<FileWriter>();
if (!writer_->Open(
path, FileWriteMode::kReuseOrCreate, FilePermissions::kOwnerOnly)) {
return false;
}
writer_->Seek(0, SEEK_END);
return true;
}

CrashReportDatabase::Envelope::Envelope(const UUID& uuid) : uuid_(uuid) {}

void CrashReportDatabase::Envelope::AddAttachments(
const std::vector<base::FilePath>& attachments) {
base::FilePath event;
std::vector<base::FilePath> breadcrumbs;
std::vector<base::FilePath> others;

for (const auto& attachment : attachments) {
#if BUILDFLAG(IS_WIN)
std::string basename = base::WideToUTF8(attachment.BaseName().value());
#else
std::string basename = attachment.BaseName().value();
#endif
if (basename == "__sentry-event") {
event = attachment;
} else if (basename.rfind("__sentry-breadcrumb", 0) == 0) {
breadcrumbs.push_back(attachment);
} else {
others.push_back(attachment);
}
}

AddEvent(event, breadcrumbs);
for (const auto& attachment : others) {
AddAttachment(attachment);
}
}

void CrashReportDatabase::Envelope::AddMinidump(FileReaderInterface* reader) {
FileOffset size = reader->Seek(0, SEEK_END);
std::string header = base::StringPrintf(
"{\"type\":\"attachment\","
"\"length\":%zu,"
"\"attachment_type\":\"event.minidump\","
"\"filename\":\"%s.dmp\"}",
static_cast<size_t>(size),
uuid_.ToString().c_str());
writer_->Write("\n", 1);
writer_->Write(header.data(), header.size());
writer_->Write("\n", 1);
reader->Seek(0, SEEK_SET);
CopyFileContent(reader, writer_.get());
}

void CrashReportDatabase::Envelope::AddEvent(
const base::FilePath& event,
const std::vector<base::FilePath>& breadcrumbs) {
std::string event_data;
if (!LoggingReadEntireFile(event, &event_data)) {
return;
}

mpack_tree_t event_obj;
mpack_tree_init_data(&event_obj, event_data.data(), event_data.size());
mpack_tree_parse(&event_obj);
std::string event_json = MpackToJsonString(mpack_tree_root(&event_obj));
mpack_tree_destroy(&event_obj);

// read all breadcrumb files
size_t max_breadcrumbs = 0;
std::vector<std::unique_ptr<mpack_tree_t, mpack_error_t (*)(mpack_tree_t*)>>
all_breadcrumbs;
std::vector<std::string> breadcrumb_datas(breadcrumbs.size());
for (size_t i = 0; i < breadcrumbs.size(); ++i) {
auto& breadcrumb_data = breadcrumb_datas[i];
if (!LoggingReadEntireFile(breadcrumbs[i], &breadcrumb_data)) {
continue;
}

size_t count = 0;
size_t offset = 0;
while (offset < breadcrumb_data.size()) {
auto breadcrumb_obj =
std::unique_ptr<mpack_tree_t, mpack_error_t (*)(mpack_tree_t*)>(
new mpack_tree_t, mpack_tree_destroy);
mpack_tree_init_data(breadcrumb_obj.get(),
breadcrumb_data.data() + offset,
breadcrumb_data.size() - offset);
mpack_tree_parse(breadcrumb_obj.get());

size_t size = mpack_tree_size(breadcrumb_obj.get());
all_breadcrumbs.push_back(std::move(breadcrumb_obj));

offset += size;
count++;
}
max_breadcrumbs = std::max(max_breadcrumbs, count);
}

// sort breadcrumbs by timestamp and limit to max_breadcrumbs
std::sort(all_breadcrumbs.begin(),
all_breadcrumbs.end(),
[](const auto& a, const auto& b) {
mpack_node_t ts_a =
mpack_node_map_cstr(mpack_tree_root(a.get()), "timestamp");
mpack_node_t ts_b =
mpack_node_map_cstr(mpack_tree_root(b.get()), "timestamp");
return strcmp(mpack_node_str(ts_a), mpack_node_str(ts_b)) < 0;
});
std::string breadcrumbs_json;
size_t start = std::max<size_t>(0, all_breadcrumbs.size() - max_breadcrumbs);
for (size_t i = start; i < all_breadcrumbs.size(); ++i) {
if (!breadcrumbs_json.empty()) {
breadcrumbs_json += ",";
}
const auto& breadcrumb_tree = all_breadcrumbs[i];
breadcrumbs_json +=
MpackToJsonString(mpack_tree_root(breadcrumb_tree.get()));
}

// write event with breadcrumbs
event_json.erase(0, 1); // leading '{'
event_json.pop_back(); // trailing '}'
std::string payload = base::StringPrintf("{%s,\"breadcrumbs\":[%s]}",
event_json.c_str(),
breadcrumbs_json.c_str());
std::string header = base::StringPrintf(
"{\"type\":\"event\","
"\"length\":%zu}",
payload.size());
writer_->Write("\n", 1);
writer_->Write(header.data(), header.size());
writer_->Write("\n", 1);
writer_->Write(payload.data(), payload.size());
writer_->Write("\n", 1);
}

void CrashReportDatabase::Envelope::AddAttachment(
const base::FilePath& attachment) {
std::string payload;
if (!LoggingReadEntireFile(attachment, &payload)) {
return;
}

#if BUILDFLAG(IS_WIN)
std::string basename = base::WideToUTF8(attachment.BaseName().value());
#else
std::string basename = attachment.BaseName().value();
#endif

std::string header = base::StringPrintf(
"{\"type\":\"attachment\","
"\"length\":%zu,"
"\"attachment_type\":\"event.attachment\","
"\"filename\": \"%s\"}",
payload.size(),
EscapeJsonString(basename).c_str());
writer_->Write("\n", 1);
writer_->Write(header.data(), header.size());
writer_->Write("\n", 1);
writer_->Write(payload.data(), payload.size());
writer_->Write("\n", 1);
}

void CrashReportDatabase::Envelope::Finish() {
writer_->Close();
}

CrashReportDatabase::OperationStatus CrashReportDatabase::RecordUploadComplete(
std::unique_ptr<const UploadReport> report_in,
const std::string& id) {
Expand Down
Loading