Skip to content

Commit 137d0f4

Browse files
feat: external crash reporter (#131)
Co-authored-by: seer-by-sentry[bot] <157164994+seer-by-sentry[bot]@users.noreply.github.com>
1 parent 3e3f281 commit 137d0f4

25 files changed

Lines changed: 16156 additions & 24 deletions

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ add_subdirectory(client)
162162

163163
add_subdirectory(third_party/zlib)
164164
add_subdirectory(third_party/getopt)
165+
add_subdirectory(third_party/mpack)
165166

166167
add_subdirectory(tools)
167168
add_subdirectory(handler)

client/client_argv_handling.cc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ std::vector<std::string> BuildHandlerArgvStrings(
3535
const std::string& http_proxy,
3636
const std::map<std::string, std::string>& annotations,
3737
const std::vector<std::string>& arguments,
38-
const std::vector<base::FilePath>& attachments) {
38+
const std::vector<base::FilePath>& attachments,
39+
const base::FilePath& crash_reporter,
40+
const base::FilePath& crash_envelope) {
3941
std::vector<std::string> argv_strings(1, handler.value());
4042

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

74+
if (!crash_reporter.empty()) {
75+
argv_strings.push_back(
76+
FormatArgumentString("crash-reporter", crash_reporter.value()));
77+
}
78+
79+
if (!crash_envelope.empty()) {
80+
argv_strings.push_back(
81+
FormatArgumentString("crash-envelope", crash_envelope.value()));
82+
}
83+
7284
return argv_strings;
7385
}
7486

client/client_argv_handling.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ std::vector<std::string> BuildHandlerArgvStrings(
3737
const std::string& http_proxy,
3838
const std::map<std::string, std::string>& annotations,
3939
const std::vector<std::string>& arguments,
40-
const std::vector<base::FilePath>& attachments = {});
40+
const std::vector<base::FilePath>& attachments = {},
41+
const base::FilePath& crash_reporter = base::FilePath(),
42+
const base::FilePath& crash_envelope = base::FilePath());
4143

4244
//! \brief Flattens a string vector into a const char* vector suitable for use
4345
//! in an exec() call.

client/crash_report_database.cc

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@
1717
#include <sys/stat.h>
1818

1919
#include "base/logging.h"
20+
#include "base/strings/stringprintf.h"
2021
#include "base/strings/utf_string_conversions.h"
2122
#include "build/build_config.h"
2223
#include "util/file/directory_reader.h"
24+
#include "util/file/file_helper.h"
2325
#include "util/file/filesystem.h"
2426

27+
#include <mpack.h>
28+
2529
namespace crashpad {
2630

2731
namespace {
@@ -75,6 +79,100 @@ base::FilePath EnsureUniqueFile(const base::FilePath& dir,
7579

7680
return unique;
7781
}
82+
83+
// Escapes a string for JSON (double quotes, backslash, control chars)
84+
std::string EscapeJsonString(const std::string& input) {
85+
std::string output;
86+
output.reserve(input.size() + 8);
87+
for (char c : input) {
88+
switch (c) {
89+
case '\"':
90+
output += "\\\"";
91+
break;
92+
case '\\':
93+
output += "\\\\";
94+
break;
95+
case '\b':
96+
output += "\\b";
97+
break;
98+
case '\f':
99+
output += "\\f";
100+
break;
101+
case '\n':
102+
output += "\\n";
103+
break;
104+
case '\r':
105+
output += "\\r";
106+
break;
107+
case '\t':
108+
output += "\\t";
109+
break;
110+
default:
111+
if (static_cast<unsigned char>(c) < 0x20) {
112+
char buf[7];
113+
snprintf(buf, sizeof(buf), "\\u%04x", c);
114+
output += buf;
115+
} else {
116+
output += c;
117+
}
118+
}
119+
}
120+
return output;
121+
}
122+
123+
std::string MpackToJsonString(mpack_node_t node) {
124+
switch (mpack_node_type(node)) {
125+
case mpack_type_nil:
126+
return "null";
127+
case mpack_type_bool:
128+
return mpack_node_bool(node) ? "true" : "false";
129+
case mpack_type_int:
130+
return std::to_string(mpack_node_i64(node));
131+
case mpack_type_uint:
132+
return std::to_string(mpack_node_u64(node));
133+
case mpack_type_float:
134+
return std::to_string(mpack_node_float(node));
135+
case mpack_type_double:
136+
return std::to_string(mpack_node_double(node));
137+
case mpack_type_str: {
138+
return "\"" +
139+
EscapeJsonString(
140+
std::string(mpack_node_str(node), mpack_node_strlen(node))) +
141+
"\"";
142+
}
143+
case mpack_type_array: {
144+
std::string result = "[";
145+
size_t n = mpack_node_array_length(node);
146+
for (size_t i = 0; i < n; ++i) {
147+
if (i > 0) {
148+
result += ",";
149+
}
150+
result += MpackToJsonString(mpack_node_array_at(node, i));
151+
}
152+
result += "]";
153+
return result;
154+
}
155+
case mpack_type_map: {
156+
std::string result = "{";
157+
size_t n = mpack_node_map_count(node);
158+
for (size_t i = 0; i < n; ++i) {
159+
if (i > 0) {
160+
result += ",";
161+
}
162+
mpack_node_t key = mpack_node_map_key_at(node, i);
163+
mpack_node_t val = mpack_node_map_value_at(node, i);
164+
std::string key_str(mpack_node_str(key), mpack_node_strlen(key));
165+
result += "\"" + key_str + "\":" + MpackToJsonString(val);
166+
}
167+
result += "}";
168+
return result;
169+
}
170+
default:
171+
LOG(ERROR) << "Unsupported mpack node type: " << mpack_node_type(node);
172+
return "";
173+
}
174+
}
175+
78176
} // namespace
79177

80178
CrashReportDatabase::Report::Report()
@@ -204,6 +302,179 @@ bool CrashReportDatabase::UploadReport::Initialize(const base::FilePath& path,
204302
return reader_->Open(path);
205303
}
206304

305+
bool CrashReportDatabase::Envelope::Initialize(const base::FilePath& path) {
306+
path_ = path;
307+
if (path.empty()) {
308+
return false;
309+
}
310+
writer_ = std::make_unique<FileWriter>();
311+
if (!writer_->Open(
312+
path, FileWriteMode::kReuseOrCreate, FilePermissions::kOwnerOnly)) {
313+
return false;
314+
}
315+
writer_->Seek(0, SEEK_END);
316+
return true;
317+
}
318+
319+
CrashReportDatabase::Envelope::Envelope(const UUID& uuid) : uuid_(uuid) {}
320+
321+
void CrashReportDatabase::Envelope::AddAttachments(
322+
const std::vector<base::FilePath>& attachments) {
323+
base::FilePath event;
324+
std::vector<base::FilePath> breadcrumbs;
325+
std::vector<base::FilePath> others;
326+
327+
for (const auto& attachment : attachments) {
328+
#if BUILDFLAG(IS_WIN)
329+
std::string basename = base::WideToUTF8(attachment.BaseName().value());
330+
#else
331+
std::string basename = attachment.BaseName().value();
332+
#endif
333+
if (basename == "__sentry-event") {
334+
event = attachment;
335+
} else if (basename.rfind("__sentry-breadcrumb", 0) == 0) {
336+
breadcrumbs.push_back(attachment);
337+
} else {
338+
others.push_back(attachment);
339+
}
340+
}
341+
342+
AddEvent(event, breadcrumbs);
343+
for (const auto& attachment : others) {
344+
AddAttachment(attachment);
345+
}
346+
}
347+
348+
void CrashReportDatabase::Envelope::AddMinidump(FileReaderInterface* reader) {
349+
FileOffset size = reader->Seek(0, SEEK_END);
350+
std::string header = base::StringPrintf(
351+
"{\"type\":\"attachment\","
352+
"\"length\":%zu,"
353+
"\"attachment_type\":\"event.minidump\","
354+
"\"filename\":\"%s.dmp\"}",
355+
static_cast<size_t>(size),
356+
uuid_.ToString().c_str());
357+
writer_->Write("\n", 1);
358+
writer_->Write(header.data(), header.size());
359+
writer_->Write("\n", 1);
360+
reader->Seek(0, SEEK_SET);
361+
CopyFileContent(reader, writer_.get());
362+
}
363+
364+
void CrashReportDatabase::Envelope::AddEvent(
365+
const base::FilePath& event,
366+
const std::vector<base::FilePath>& breadcrumbs) {
367+
std::string event_data;
368+
if (!LoggingReadEntireFile(event, &event_data)) {
369+
return;
370+
}
371+
372+
mpack_tree_t event_obj;
373+
mpack_tree_init_data(&event_obj, event_data.data(), event_data.size());
374+
mpack_tree_parse(&event_obj);
375+
std::string event_json = MpackToJsonString(mpack_tree_root(&event_obj));
376+
mpack_tree_destroy(&event_obj);
377+
378+
// read all breadcrumb files
379+
size_t max_breadcrumbs = 0;
380+
std::vector<std::unique_ptr<mpack_tree_t, mpack_error_t (*)(mpack_tree_t*)>>
381+
all_breadcrumbs;
382+
std::vector<std::string> breadcrumb_datas(breadcrumbs.size());
383+
for (size_t i = 0; i < breadcrumbs.size(); ++i) {
384+
auto& breadcrumb_data = breadcrumb_datas[i];
385+
if (!LoggingReadEntireFile(breadcrumbs[i], &breadcrumb_data)) {
386+
continue;
387+
}
388+
389+
size_t count = 0;
390+
size_t offset = 0;
391+
while (offset < breadcrumb_data.size()) {
392+
auto breadcrumb_obj =
393+
std::unique_ptr<mpack_tree_t, mpack_error_t (*)(mpack_tree_t*)>(
394+
new mpack_tree_t, mpack_tree_destroy);
395+
mpack_tree_init_data(breadcrumb_obj.get(),
396+
breadcrumb_data.data() + offset,
397+
breadcrumb_data.size() - offset);
398+
mpack_tree_parse(breadcrumb_obj.get());
399+
400+
size_t size = mpack_tree_size(breadcrumb_obj.get());
401+
all_breadcrumbs.push_back(std::move(breadcrumb_obj));
402+
403+
offset += size;
404+
count++;
405+
}
406+
max_breadcrumbs = std::max(max_breadcrumbs, count);
407+
}
408+
409+
// sort breadcrumbs by timestamp and limit to max_breadcrumbs
410+
std::sort(all_breadcrumbs.begin(),
411+
all_breadcrumbs.end(),
412+
[](const auto& a, const auto& b) {
413+
mpack_node_t ts_a =
414+
mpack_node_map_cstr(mpack_tree_root(a.get()), "timestamp");
415+
mpack_node_t ts_b =
416+
mpack_node_map_cstr(mpack_tree_root(b.get()), "timestamp");
417+
return strcmp(mpack_node_str(ts_a), mpack_node_str(ts_b)) < 0;
418+
});
419+
std::string breadcrumbs_json;
420+
size_t start = std::max<size_t>(0, all_breadcrumbs.size() - max_breadcrumbs);
421+
for (size_t i = start; i < all_breadcrumbs.size(); ++i) {
422+
if (!breadcrumbs_json.empty()) {
423+
breadcrumbs_json += ",";
424+
}
425+
const auto& breadcrumb_tree = all_breadcrumbs[i];
426+
breadcrumbs_json +=
427+
MpackToJsonString(mpack_tree_root(breadcrumb_tree.get()));
428+
}
429+
430+
// write event with breadcrumbs
431+
event_json.erase(0, 1); // leading '{'
432+
event_json.pop_back(); // trailing '}'
433+
std::string payload = base::StringPrintf("{%s,\"breadcrumbs\":[%s]}",
434+
event_json.c_str(),
435+
breadcrumbs_json.c_str());
436+
std::string header = base::StringPrintf(
437+
"{\"type\":\"event\","
438+
"\"length\":%zu}",
439+
payload.size());
440+
writer_->Write("\n", 1);
441+
writer_->Write(header.data(), header.size());
442+
writer_->Write("\n", 1);
443+
writer_->Write(payload.data(), payload.size());
444+
writer_->Write("\n", 1);
445+
}
446+
447+
void CrashReportDatabase::Envelope::AddAttachment(
448+
const base::FilePath& attachment) {
449+
std::string payload;
450+
if (!LoggingReadEntireFile(attachment, &payload)) {
451+
return;
452+
}
453+
454+
#if BUILDFLAG(IS_WIN)
455+
std::string basename = base::WideToUTF8(attachment.BaseName().value());
456+
#else
457+
std::string basename = attachment.BaseName().value();
458+
#endif
459+
460+
std::string header = base::StringPrintf(
461+
"{\"type\":\"attachment\","
462+
"\"length\":%zu,"
463+
"\"attachment_type\":\"event.attachment\","
464+
"\"filename\": \"%s\"}",
465+
payload.size(),
466+
EscapeJsonString(basename).c_str());
467+
writer_->Write("\n", 1);
468+
writer_->Write(header.data(), header.size());
469+
writer_->Write("\n", 1);
470+
writer_->Write(payload.data(), payload.size());
471+
writer_->Write("\n", 1);
472+
}
473+
474+
void CrashReportDatabase::Envelope::Finish() {
475+
writer_->Close();
476+
}
477+
207478
CrashReportDatabase::OperationStatus CrashReportDatabase::RecordUploadComplete(
208479
std::unique_ptr<const UploadReport> report_in,
209480
const std::string& id) {

0 commit comments

Comments
 (0)