Skip to content

Commit f8f356c

Browse files
authored
Add remote symbolication support with build-id and PC offset (#324)
1 parent 5196856 commit f8f356c

37 files changed

Lines changed: 2042 additions & 97 deletions

.claude/commands/build-and-summarize

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,4 @@ echo
7272
echo "Delegating to gradle-logs-analyst agent…"
7373
# If your CLI supports non-streaming, set it here to avoid verbose output.
7474
# Example (uncomment if supported): export CLAUDE_NO_STREAM=1
75-
claude "Act as the gradle-logs-analyst agent to parse the build log at: $LOG. Generate the required Gradle summary artifacts as specified in the gradle-logs-analyst agent definition."
75+
claude "Act as the gradle-logs-analyst agent to parse the build log at: $LOG. Generate the required Gradle summary artifacts as specified in the gradle-logs-analyst agent definition."
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# build-and-summarize
22

3-
Runs `./gradlew` with full output captured to a timestamped log, shows minimal live progress (task starts + final build/test summary), then asks the `gradle-logs-analyst` agent to produce structured artifacts from the log.
3+
Execute the bash script `~/.claude/commands/build-and-summarize` with all provided arguments.
44

5-
## Usage
6-
```bash
7-
./.claude/commands/build-and-summarize [<gradle-args>...]
5+
This script will:
6+
- Run `./gradlew` with the specified arguments (defaults to 'build' if none provided)
7+
- Capture full output to a timestamped log in `build/logs/`
8+
- Show minimal live progress in the console
9+
- Delegate to the `gradle-logs-analyst` agent for structured analysis
10+
11+
Pass through all arguments exactly as provided by the user.

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,25 @@ Improved thread-local storage initialization to prevent race conditions:
402402

403403
These architectural improvements focus on eliminating race conditions, improving performance in high-throughput scenarios, and providing better debugging capabilities for the native profiling engine.
404404

405+
### Remote Symbolication Support (2025)
406+
407+
Added support for remote symbolication to enable offloading symbol resolution from the agent to backend services:
408+
409+
- **Build-ID extraction**: Automatically extracts GNU build-id from ELF binaries on Linux
410+
- **Raw addressing information**: Stores build-id and PC offset instead of resolved symbol names
411+
- **Remote symbolication mode**: Enable with `remotesym=true` profiler argument
412+
- **JFR integration**: Remote frames serialized with build-id and offset for backend resolution
413+
- **Zero encoding overhead**: Uses dedicated frame type (FRAME_NATIVE_REMOTE) for efficient serialization
414+
415+
**Benefits**:
416+
- Reduces agent overhead by eliminating local symbol resolution
417+
- Enables centralized symbol resolution with better caching
418+
- Supports scenarios where debug symbols are not available locally
419+
420+
**Key files**: `elfBuildId.h`, `elfBuildId.cpp`, `profiler.cpp`, `flightRecorder.cpp`
421+
422+
For detailed documentation, see [doc/RemoteSymbolication.md](doc/RemoteSymbolication.md).
423+
405424
## Contributing
406425
1. Fork the repository
407426
2. Create a feature branch

ddprof-lib/src/main/cpp/arguments.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright 2017 Andrei Pangin
3+
* Copyright 2026, Datadog, Inc.
34
*
45
* Licensed under the Apache License, Version 2.0 (the "License");
56
* you may not use this file except in compliance with the License.
@@ -88,7 +89,10 @@ static const Multiplier UNIVERSAL[] = {
8889
// samples
8990
// generations - track surviving generations
9091
// lightweight[=BOOL] - enable lightweight profiling - events without
91-
// stacktraces (default: true) jfr - dump events in Java
92+
// stacktraces (default: true)
93+
// remotesym[=BOOL] - enable remote symbolication for native frames
94+
// (stores build-id and PC offset instead of symbol names)
95+
// jfr - dump events in Java
9296
// Flight Recorder format interval=N - sampling interval in ns
9397
// (default: 10'000'000, i.e. 10 ms) jstackdepth=N - maximum Java stack
9498
// depth (default: 2048) safemode=BITS - disable stack recovery
@@ -339,16 +343,35 @@ Error Arguments::parse(const char *args) {
339343
_enable_method_cleanup = true;
340344
}
341345

342-
CASE("wallsampler")
346+
CASE("remotesym")
343347
if (value != NULL) {
344348
switch (value[0]) {
345-
case 'j':
346-
_wallclock_sampler = JVMTI;
349+
case 'n': // no
350+
case 'f': // false
351+
case '0': // 0
352+
_remote_symbolication = false;
347353
break;
348-
case 'a':
354+
case 'y': // yes
355+
case 't': // true
356+
case '1': // 1
349357
default:
350-
_wallclock_sampler = ASGCT;
358+
_remote_symbolication = true;
351359
}
360+
} else {
361+
// No value means enable
362+
_remote_symbolication = true;
363+
}
364+
365+
CASE("wallsampler")
366+
if (value != NULL) {
367+
switch (value[0]) {
368+
case 'j':
369+
_wallclock_sampler = JVMTI;
370+
break;
371+
case 'a':
372+
default:
373+
_wallclock_sampler = ASGCT;
374+
}
352375
}
353376

354377
DEFAULT()

ddprof-lib/src/main/cpp/arguments.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ class Arguments {
188188
std::vector<std::string> _context_attributes;
189189
bool _lightweight;
190190
bool _enable_method_cleanup;
191+
bool _remote_symbolication; // Enable remote symbolication for native frames
191192

192193
Arguments(bool persistent = false)
193194
: _buf(NULL),
@@ -221,7 +222,8 @@ class Arguments {
221222
_context_attributes({}),
222223
_wallclock_sampler(ASGCT),
223224
_lightweight(false),
224-
_enable_method_cleanup(true) {}
225+
_enable_method_cleanup(true),
226+
_remote_symbolication(false) {}
225227

226228
~Arguments();
227229

ddprof-lib/src/main/cpp/codeCache.cpp

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ CodeCache::CodeCache(const char *name, short lib_index,
3737
_plt_size = 0;
3838
_debug_symbols = false;
3939

40+
// Initialize build-id fields
41+
_build_id = nullptr;
42+
_build_id_len = 0;
43+
_load_bias = 0;
44+
4045
memset(_imports, 0, sizeof(_imports));
4146
_imports_patchable = imports_patchable;
4247

@@ -48,16 +53,33 @@ CodeCache::CodeCache(const char *name, short lib_index,
4853
_blobs = new CodeBlob[_capacity];
4954
}
5055

51-
CodeCache::CodeCache(const CodeCache &other) {
56+
void CodeCache::copyFrom(const CodeCache& other) {
5257
_name = NativeFunc::create(other._name, -1);
5358
_lib_index = other._lib_index;
5459
_min_address = other._min_address;
5560
_max_address = other._max_address;
5661
_text_base = other._text_base;
62+
_image_base = other._image_base;
5763

58-
_imports_patchable = other._imports_patchable;
5964
_plt_offset = other._plt_offset;
6065
_plt_size = other._plt_size;
66+
_debug_symbols = other._debug_symbols;
67+
68+
// Copy build-id information
69+
_build_id_len = other._build_id_len;
70+
if (other._build_id != nullptr && other._build_id_len > 0) {
71+
size_t hex_str_len = strlen(other._build_id);
72+
_build_id = static_cast<char*>(malloc(hex_str_len + 1));
73+
if (_build_id != nullptr) {
74+
strcpy(_build_id, other._build_id);
75+
}
76+
} else {
77+
_build_id = nullptr;
78+
}
79+
_load_bias = other._load_bias;
80+
81+
memset(_imports, 0, sizeof(_imports));
82+
_imports_patchable = other._imports_patchable;
6183

6284
_dwarf_table_length = other._dwarf_table_length;
6385
_dwarf_table = new FrameDesc[_dwarf_table_length];
@@ -70,37 +92,23 @@ CodeCache::CodeCache(const CodeCache &other) {
7092
memcpy(_blobs, other._blobs, _count * sizeof(CodeBlob));
7193
}
7294

95+
CodeCache::CodeCache(const CodeCache &other) {
96+
copyFrom(other);
97+
}
98+
7399
CodeCache &CodeCache::operator=(const CodeCache &other) {
74100
if (&other == this) {
75101
return *this;
76-
} else {
77-
delete _name;
78-
delete _dwarf_table;
79-
delete _blobs;
80-
81-
_name = NativeFunc::create(other._name, -1);
82-
_lib_index = other._lib_index;
83-
_min_address = other._min_address;
84-
_max_address = other._max_address;
85-
_text_base = other._text_base;
86-
87-
_imports_patchable = other._imports_patchable;
88-
89-
_plt_offset = other._plt_offset;
90-
_plt_size = other._plt_size;
102+
}
91103

92-
_dwarf_table_length = other._dwarf_table_length;
93-
_dwarf_table = new FrameDesc[_dwarf_table_length];
94-
memcpy(_dwarf_table, other._dwarf_table,
95-
_dwarf_table_length * sizeof(FrameDesc));
104+
NativeFunc::destroy(_name);
105+
delete[] _dwarf_table;
106+
delete[] _blobs;
107+
free(_build_id);
96108

97-
_capacity = other._capacity;
98-
_count = other._count;
99-
_blobs = new CodeBlob[_capacity];
100-
memcpy(_blobs, other._blobs, _count * sizeof(CodeBlob));
109+
copyFrom(other);
101110

102-
return *this;
103-
}
111+
return *this;
104112
}
105113

106114
CodeCache::~CodeCache() {
@@ -109,7 +117,8 @@ CodeCache::~CodeCache() {
109117
}
110118
NativeFunc::destroy(_name);
111119
delete[] _blobs;
112-
delete _dwarf_table;
120+
delete[] _dwarf_table;
121+
free(_build_id); // Free build-id memory
113122
}
114123

115124
void CodeCache::expand() {
@@ -387,3 +396,24 @@ FrameDesc CodeCache::findFrameDesc(const void *pc) {
387396
return FrameDesc::default_frame;
388397
}
389398
}
399+
400+
void CodeCache::setBuildId(const char* build_id, size_t build_id_len) {
401+
// Free existing build-id if any
402+
free(_build_id);
403+
_build_id = nullptr;
404+
_build_id_len = 0;
405+
406+
if (build_id != nullptr && build_id_len > 0) {
407+
// build_id is a hex string, allocate based on actual string length
408+
size_t hex_str_len = strlen(build_id);
409+
_build_id = static_cast<char*>(malloc(hex_str_len + 1));
410+
411+
if (_build_id != nullptr) {
412+
// Copy the hex string
413+
strcpy(_build_id, build_id);
414+
// Store the original byte length (not hex string length)
415+
_build_id_len = build_id_len;
416+
}
417+
}
418+
}
419+

ddprof-lib/src/main/cpp/codeCache.h

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ class CodeCache {
116116
unsigned int _plt_offset;
117117
unsigned int _plt_size;
118118

119+
// Build-ID and load bias for remote symbolication
120+
char *_build_id; // GNU build-id (hex string, null if not available)
121+
size_t _build_id_len; // Build-id length in bytes (raw, not hex string length)
122+
uintptr_t _load_bias; // Load bias (image_base - file_base address)
123+
119124
void **_imports[NUM_IMPORTS][NUM_IMPORT_TYPES];
120125
bool _imports_patchable;
121126
bool _debug_symbols;
@@ -130,6 +135,7 @@ class CodeCache {
130135
void expand();
131136
void makeImportsPatchable();
132137
void saveImport(ImportId id, void** entry);
138+
void copyFrom(const CodeCache& other);
133139

134140
public:
135141
explicit CodeCache(const char *name, short lib_index = -1,
@@ -169,10 +175,30 @@ class CodeCache {
169175

170176
void setDebugSymbols(bool debug_symbols) { _debug_symbols = debug_symbols; }
171177

178+
// Build-ID and remote symbolication support
179+
const char* buildId() const { return _build_id; }
180+
size_t buildIdLen() const { return _build_id_len; }
181+
bool hasBuildId() const { return _build_id != nullptr; }
182+
uintptr_t loadBias() const { return _load_bias; }
183+
short libIndex() const { return _lib_index; }
184+
185+
// Sets the build-id (hex string) and stores the original byte length
186+
// build_id: null-terminated hex string (e.g., "abc123..." for 40-char string)
187+
// build_id_len: original byte length before hex conversion (e.g., 20 bytes)
188+
void setBuildId(const char* build_id, size_t build_id_len);
189+
void setLoadBias(uintptr_t load_bias) { _load_bias = load_bias; }
190+
172191
void add(const void *start, int length, const char *name,
173192
bool update_bounds = false);
174193
void updateBounds(const void *start, const void *end);
175194
void sort();
195+
196+
/**
197+
* Mark symbols matching the predicate with the given mark value.
198+
*
199+
* This is called during profiler initialization to mark JVM internal functions
200+
* (MARK_VM_RUNTIME, MARK_INTERPRETER, MARK_COMPILER_ENTRY, MARK_ASYNC_PROFILER).
201+
*/
176202
template <typename NamePredicate>
177203
inline void mark(NamePredicate predicate, char value) {
178204
for (int i = 0; i < _count; i++) {
@@ -225,7 +251,7 @@ class CodeCacheArray {
225251
memset(_libs, 0, MAX_NATIVE_LIBS * sizeof(CodeCache *));
226252
}
227253

228-
CodeCache *operator[](int index) { return _libs[index]; }
254+
CodeCache *operator[](int index) const { return __atomic_load_n(&_libs[index], __ATOMIC_ACQUIRE); }
229255

230256
int count() const { return __atomic_load_n(&_count, __ATOMIC_RELAXED); }
231257

@@ -247,7 +273,7 @@ class CodeCacheArray {
247273
return lib;
248274
}
249275

250-
size_t memoryUsage() {
276+
size_t memoryUsage() const {
251277
return __atomic_load_n(&_used_memory, __ATOMIC_RELAXED);
252278
}
253279
};

ddprof-lib/src/main/cpp/counters.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@
6464
X(UNWINDING_TIME_ASYNC, "unwinding_ticks_async") \
6565
X(UNWINDING_TIME_JVMTI, "unwinding_ticks_jvmti") \
6666
X(CALLTRACE_STORAGE_DROPPED, "calltrace_storage_dropped_traces") \
67-
X(LINE_NUMBER_TABLES, "line_number_tables")
67+
X(LINE_NUMBER_TABLES, "line_number_tables") \
68+
X(REMOTE_SYMBOLICATION_FRAMES, "remote_symbolication_frames") \
69+
X(REMOTE_SYMBOLICATION_LIBS_WITH_BUILD_ID, "remote_symbolication_libs_with_build_id") \
70+
X(REMOTE_SYMBOLICATION_BUILD_ID_CACHE_HITS, "remote_symbolication_build_id_cache_hits")
6871
#define X_ENUM(a, b) a,
6972
typedef enum CounterId : int {
7073
DD_COUNTER_TABLE(X_ENUM) DD_NUM_COUNTERS

0 commit comments

Comments
 (0)