Skip to content
Draft
Changes from all commits
Commits
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
79 changes: 54 additions & 25 deletions backtrace-library/src/main/cpp/backends/crashpad-backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
#include "handler/handler_main.h"
#include "handler/crash_report_upload_thread.h"
#include "backtrace-native.h"
#include "client/annotation.h"
#include <jni.h>
#include <libgen.h>
#include <cstring>
#include <unordered_map>

extern std::string thread_id;
extern std::atomic_bool initialized;
Expand Down Expand Up @@ -56,6 +59,50 @@ namespace {
"Started Crashpad upload thread for offline native reports");
}

// Stores each attribute as a crashpad StringAnnotation in the global
// AnnotationList (unbounded), replacing the legacy simple_annotations
// dictionary that was limited to 64 entries. Once set, an annotation is
// permanently referenced by crashpad's global list, so annotations and
// their name buffers are allocated once and never freed.
constexpr crashpad::Annotation::ValueSizeType kAnnotationValueMaxSize = 4096;
using DynamicAnnotation = crashpad::StringAnnotation<kAnnotationValueMaxSize>;

std::unordered_map<std::string, DynamicAnnotation*> g_annotations;

// Caller must hold attribute_synchronization.
DynamicAnnotation* GetOrCreateAnnotation(const std::string& key) {
auto it = g_annotations.find(key);
if (it != g_annotations.end()) {
return it->second;
}
char* nameCopy = new char[key.size() + 1];
std::memcpy(nameCopy, key.c_str(), key.size() + 1);
DynamicAnnotation* annotation = new DynamicAnnotation(nameCopy);
g_annotations.emplace(key, annotation);
return annotation;
}

void SetAnnotation(const char* rawKey, const char* rawValue) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One compatibility edge case: empty string values may no longer be represented.

With StringAnnotation, Set(base::StringPiece("")) results in size 0, which Crashpad treats as cleared/not included. The previous SimpleStringDictionary considered an entry active based on the key, so an empty value could still be present.

Could we either document that native attributes with empty string values are skipped, or preserve the old behaviour?
a test would be helpful either way: client.addAttribute("empty.attribute", "");

If preserving empty values is important, we may need a special path for empty values, since AnnotationList cannot distinguish “set to empty string” from “cleared” using size 0.

if (rawKey == nullptr || rawKey[0] == '\0') {
return;
}
const std::lock_guard<std::mutex> lock(attribute_synchronization);
DynamicAnnotation* annotation = GetOrCreateAnnotation(std::string(rawKey));
// Set(const char*) uses strncpy; the StringPiece overload uses std::copy.
annotation->Set(base::StringPiece(rawValue != nullptr ? rawValue : ""));
}

void ClearAnnotation(const char* rawKey) {
if (rawKey == nullptr || rawKey[0] == '\0') {
return;
}
const std::lock_guard<std::mutex> lock(attribute_synchronization);
auto it = g_annotations.find(std::string(rawKey));
if (it != g_annotations.end()) {
it->second->Clear();
}
}

} // namespace

std::vector<std::string>
Expand Down Expand Up @@ -325,38 +372,29 @@ void DumpWithoutCrashCrashpad(jstring message, jboolean set_main_thread_as_fault
crashpad::CaptureContext(&context);

// set dump message for single report
crashpad::SimpleStringDictionary *annotations = NULL;

if (message != NULL || set_main_thread_as_faulting_thread == true) {
JNIEnv *env = GetJniEnv();
if (env == nullptr) {
__android_log_print(ANDROID_LOG_ERROR, "Backtrace-Android", "Cannot initialize JNIEnv");
return;
}
const std::lock_guard<std::mutex> lock(attribute_synchronization);
crashpad::CrashpadInfo *info = crashpad::CrashpadInfo::GetCrashpadInfo();
annotations = info->simple_annotations();
if (!annotations) {
annotations = new crashpad::SimpleStringDictionary();
info->set_simple_annotations(annotations);
}
if (set_main_thread_as_faulting_thread == true) {
annotations->SetKeyValue("_mod_faulting_tid", thread_id);
SetAnnotation("_mod_faulting_tid", thread_id.c_str());
}
if (message != NULL) {
// user can't override error.message - exception message that Crashpad/crash-reporting tool
// will set to tell user about error message. This code will set error.message only for single
// report and after creating a dump, method will clean up this attribute.
jboolean isCopy;
const char *rawMessage = env->GetStringUTFChars(message, &isCopy);
annotations->SetKeyValue("error.message", rawMessage);
SetAnnotation("error.message", rawMessage);
env->ReleaseStringUTFChars(message, rawMessage);
}
}
client->DumpWithoutCrash(&context);

if (annotations != NULL) {
annotations->RemoveKey("error.message");
if (message != NULL) {
ClearAnnotation("error.message");
}
}

Expand All @@ -372,22 +410,13 @@ void AddAttributeCrashpad(jstring key, jstring value) {
return;
}

const std::lock_guard<std::mutex> lock(attribute_synchronization);
crashpad::CrashpadInfo *info = crashpad::CrashpadInfo::GetCrashpadInfo();
crashpad::SimpleStringDictionary *annotations = info->simple_annotations();
if (!annotations) {
annotations = new crashpad::SimpleStringDictionary();
info->set_simple_annotations(annotations);
}

jboolean isCopy;
const char *crashpadKey = env->GetStringUTFChars(key, &isCopy);
const char *crashpadValue = env->GetStringUTFChars(value, &isCopy);
if (crashpadKey && crashpadValue)
annotations->SetKeyValue(crashpadKey, crashpadValue);
SetAnnotation(crashpadKey, crashpadValue);

env->ReleaseStringUTFChars(key, crashpadKey);
env->ReleaseStringUTFChars(value, crashpadValue);
if (crashpadKey) env->ReleaseStringUTFChars(key, crashpadKey);
if (crashpadValue) env->ReleaseStringUTFChars(value, crashpadValue);
}

void AddAttachmentCrashpad(jstring jattachment) {
Expand Down
Loading