Skip to content

Commit 9b9f99a

Browse files
authored
feature: support more than 64 native attributes (#213)
Removes the 64-entry limit on native (Crashpad) attributes. Previously, a simple dictionary was used for storing attributes, which limits the total count to 64 entries. They are now stored in Crashpad's global AnnotationList. The attribute count is limited by Crashpad's internal limit, kMaxNumberOfAnnotations. SetAnnotation will refuse to add keys beyond the limit, providing feedback with a warning.
1 parent 76046b4 commit 9b9f99a

3 files changed

Lines changed: 90 additions & 25 deletions

File tree

backtrace-library/src/main/cpp/backends/crashpad-backend.cpp

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
#include "handler/handler_main.h"
33
#include "handler/crash_report_upload_thread.h"
44
#include "backtrace-native.h"
5+
#include "client/annotation.h"
6+
#include "snapshot/snapshot_constants.h"
57
#include <jni.h>
68
#include <libgen.h>
9+
#include <cstring>
10+
#include <unordered_map>
711

812
extern std::string thread_id;
913
extern std::atomic_bool initialized;
@@ -56,6 +60,78 @@ namespace {
5660
"Started Crashpad upload thread for offline native reports");
5761
}
5862

63+
// Stores attributes as crashpad StringAnnotations, replacing the 64-entry
64+
// simple_annotations dictionary. The crashpad snapshot reader reads at most
65+
// crashpad::kMaxNumberOfAnnotations annotation objects per module, so
66+
// SetAnnotation drops new user keys past that limit (internal keys keep
67+
// reserved slots). Annotations and name buffers are allocated once and never
68+
// freed (the global list references them for the process lifetime).
69+
constexpr crashpad::Annotation::ValueSizeType kAnnotationValueMaxSize = 4096;
70+
using DynamicAnnotation = crashpad::StringAnnotation<kAnnotationValueMaxSize>;
71+
72+
// Reserve slots for internal, per-report annotations so they are never
73+
// crowded out by user attributes near the read limit.
74+
constexpr size_t kReservedAnnotationSlots = 2; // error.message, _mod_faulting_tid
75+
76+
bool IsValidAnnotationKey(const char* rawKey) {
77+
return rawKey != nullptr && rawKey[0] != '\0';
78+
}
79+
80+
bool IsReservedAnnotationKey(const std::string& key) {
81+
return key == "error.message" || key == "_mod_faulting_tid";
82+
}
83+
84+
std::unordered_map<std::string, DynamicAnnotation*> g_annotations;
85+
86+
// Caller must hold attribute_synchronization.
87+
DynamicAnnotation* GetOrCreateAnnotation(const std::string& key) {
88+
auto it = g_annotations.find(key);
89+
if (it != g_annotations.end()) {
90+
return it->second;
91+
}
92+
char* nameCopy = new char[key.size() + 1];
93+
std::memcpy(nameCopy, key.c_str(), key.size() + 1);
94+
DynamicAnnotation* annotation = new DynamicAnnotation(nameCopy);
95+
g_annotations.emplace(key, annotation);
96+
return annotation;
97+
}
98+
99+
void SetAnnotation(const char* rawKey, const char* rawValue) {
100+
if (!IsValidAnnotationKey(rawKey)) {
101+
return;
102+
}
103+
const std::lock_guard<std::mutex> lock(attribute_synchronization);
104+
std::string key(rawKey);
105+
const bool isNewKey = g_annotations.find(key) == g_annotations.end();
106+
// Cap user keys below the read limit so the reserved internal keys can
107+
// always be registered; updates to existing keys are always allowed.
108+
const size_t userAnnotationLimit =
109+
crashpad::kMaxNumberOfAnnotations - kReservedAnnotationSlots;
110+
if (isNewKey && !IsReservedAnnotationKey(key)
111+
&& g_annotations.size() >= userAnnotationLimit) {
112+
__android_log_print(
113+
ANDROID_LOG_WARN,
114+
"Backtrace-Android",
115+
"Dropping native attribute '%s': reached crashpad's annotation read limit",
116+
rawKey);
117+
return;
118+
}
119+
DynamicAnnotation* annotation = GetOrCreateAnnotation(key);
120+
// Set(const char*) uses strncpy; the StringPiece overload uses std::copy.
121+
annotation->Set(base::StringPiece(rawValue != nullptr ? rawValue : ""));
122+
}
123+
124+
void ClearAnnotation(const char* rawKey) {
125+
if (rawKey == nullptr || rawKey[0] == '\0') {
126+
return;
127+
}
128+
const std::lock_guard<std::mutex> lock(attribute_synchronization);
129+
auto it = g_annotations.find(std::string(rawKey));
130+
if (it != g_annotations.end()) {
131+
it->second->Clear();
132+
}
133+
}
134+
59135
} // namespace
60136

61137
std::vector<std::string>
@@ -325,38 +401,29 @@ void DumpWithoutCrashCrashpad(jstring message, jboolean set_main_thread_as_fault
325401
crashpad::CaptureContext(&context);
326402

327403
// set dump message for single report
328-
crashpad::SimpleStringDictionary *annotations = NULL;
329-
330404
if (message != NULL || set_main_thread_as_faulting_thread == true) {
331405
JNIEnv *env = GetJniEnv();
332406
if (env == nullptr) {
333407
__android_log_print(ANDROID_LOG_ERROR, "Backtrace-Android", "Cannot initialize JNIEnv");
334408
return;
335409
}
336-
const std::lock_guard<std::mutex> lock(attribute_synchronization);
337-
crashpad::CrashpadInfo *info = crashpad::CrashpadInfo::GetCrashpadInfo();
338-
annotations = info->simple_annotations();
339-
if (!annotations) {
340-
annotations = new crashpad::SimpleStringDictionary();
341-
info->set_simple_annotations(annotations);
342-
}
343410
if (set_main_thread_as_faulting_thread == true) {
344-
annotations->SetKeyValue("_mod_faulting_tid", thread_id);
411+
SetAnnotation("_mod_faulting_tid", thread_id.c_str());
345412
}
346413
if (message != NULL) {
347414
// user can't override error.message - exception message that Crashpad/crash-reporting tool
348415
// will set to tell user about error message. This code will set error.message only for single
349416
// report and after creating a dump, method will clean up this attribute.
350417
jboolean isCopy;
351418
const char *rawMessage = env->GetStringUTFChars(message, &isCopy);
352-
annotations->SetKeyValue("error.message", rawMessage);
419+
SetAnnotation("error.message", rawMessage);
353420
env->ReleaseStringUTFChars(message, rawMessage);
354421
}
355422
}
356423
client->DumpWithoutCrash(&context);
357424

358-
if (annotations != NULL) {
359-
annotations->RemoveKey("error.message");
425+
if (message != NULL) {
426+
ClearAnnotation("error.message");
360427
}
361428
}
362429

@@ -372,22 +439,13 @@ void AddAttributeCrashpad(jstring key, jstring value) {
372439
return;
373440
}
374441

375-
const std::lock_guard<std::mutex> lock(attribute_synchronization);
376-
crashpad::CrashpadInfo *info = crashpad::CrashpadInfo::GetCrashpadInfo();
377-
crashpad::SimpleStringDictionary *annotations = info->simple_annotations();
378-
if (!annotations) {
379-
annotations = new crashpad::SimpleStringDictionary();
380-
info->set_simple_annotations(annotations);
381-
}
382-
383442
jboolean isCopy;
384443
const char *crashpadKey = env->GetStringUTFChars(key, &isCopy);
385444
const char *crashpadValue = env->GetStringUTFChars(value, &isCopy);
386-
if (crashpadKey && crashpadValue)
387-
annotations->SetKeyValue(crashpadKey, crashpadValue);
445+
SetAnnotation(crashpadKey, crashpadValue);
388446

389-
env->ReleaseStringUTFChars(key, crashpadKey);
390-
env->ReleaseStringUTFChars(value, crashpadValue);
447+
if (crashpadKey) env->ReleaseStringUTFChars(key, crashpadKey);
448+
if (crashpadValue) env->ReleaseStringUTFChars(value, crashpadValue);
391449
}
392450

393451
void AddAttachmentCrashpad(jstring jattachment) {

backtrace-library/src/main/java/backtraceio/library/base/BacktraceBase.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,9 @@ public Map<String, Object> getAttributes() {
357357
* - is not an object (the attribute value is primitive type like String, or
358358
* Int)
359359
*
360+
* Note: native crash reports omit attributes with an empty-string value
361+
* (managed reports are unaffected).
362+
*
360363
* @param key attribute name
361364
* @param value attribute value.
362365
*/

backtrace-library/src/main/java/backtraceio/library/interfaces/Client.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public interface Client {
2323
* Adds new attributes to the client.
2424
* If the native integration is available and attributes are primitive type,
2525
* they will be added to the native reports.
26+
* Note: native crash reports omit attributes with an empty-string value
27+
* (managed reports are unaffected).
2628
* @param attributes client Attributes
2729
*/
2830
void addAttribute(Map<String, Object> attributes);
@@ -31,6 +33,8 @@ public interface Client {
3133
* Adds new attribute to the client.
3234
* If the native integration is available and attributes are primitive type,
3335
* they will be added to the native reports.
36+
* Note: native crash reports omit attributes with an empty-string value
37+
* (managed reports are unaffected).
3438
* @param key attribute key
3539
* @param value attribute value
3640
*/

0 commit comments

Comments
 (0)