Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c52da1c
Add remote symbolication support with build-id and PC offset
jbachorik Jan 7, 2026
bd80f4c
Fix RemoteSymbolicationTest assertions to match JMC stack trace format
jbachorik Jan 7, 2026
04a7e9c
Add debug output to RemoteSymbolicationTest to diagnose failure
jbachorik Jan 7, 2026
161ad44
Extend jdk.NativeLibrary with buildId and loadBias fields
jbachorik Jan 7, 2026
55fbce3
Add test library with build-id for remote symbolication testing
jbachorik Jan 7, 2026
0f3951b
Add 'vm' cstack mode to RemoteSymbolicationTest
jbachorik Jan 7, 2026
dde78ee
Refactor build-id extraction to follow project architecture
jbachorik Jan 7, 2026
a98db7a
Fix compilation errors in remote symbolication test
jbachorik Jan 7, 2026
8604a97
Add missing cstdint include for uint8_t type
jbachorik Jan 7, 2026
0fa6f7d
Fix compilation errors and enhance remote symbolication test
jbachorik Jan 7, 2026
6924e55
Fix jdk.NativeLibrary events not being emitted
jbachorik Jan 7, 2026
ff8476f
Deduplicate native_libs collections in Profiler and Libraries
jbachorik Jan 7, 2026
fd63395
Fix remote symbolication by deferring symbol resolution
jbachorik Jan 7, 2026
ef0b7e1
Increase native workload in RemoteSymbolicationTest
jbachorik Jan 7, 2026
d63365e
Fix RemoteSymbolicationTest frame detection
jbachorik Jan 7, 2026
1b7b1cc
Add debug logging for remote symbolication troubleshooting
jbachorik Jan 7, 2026
e3d9a43
Add missing common.h include for TEST_LOG macro
jbachorik Jan 7, 2026
8d758bf
Apply remote symbolication to VM/VMX stack walkers
jbachorik Jan 8, 2026
1215dc1
Implement signal-safe pre-allocated pool for RemoteFrameInfo
jbachorik Jan 13, 2026
0e4235c
Fix remote symbolication for VM/VMX stack walkers
jbachorik Jan 13, 2026
45d0122
Fix the 'remotesym' arg name
jbachorik Jan 13, 2026
3fa35e2
Update REMOTE_SYMBOLICATION.md with current implementation
jbachorik Jan 13, 2026
c90644d
Fix remote symbolication pool exhaustion on profiler restart
jbachorik Jan 15, 2026
74a9efe
Add unconditional pool reset on profiler start
jbachorik Jan 15, 2026
0fe5b65
Fix remote symbolication for dynamically loaded libraries
jbachorik Jan 15, 2026
f4d2a8f
Optimize build-ID extraction with explicit caching for O(1) performance
jbachorik Jan 15, 2026
08d31e1
Fix VM/VMX stack walks to handle packed remote symbolication data
jbachorik Jan 16, 2026
35ebb70
Refactor remote symbolication to pack marks and simplify implementation
jbachorik Jan 16, 2026
f193752
Address code review feedback for remote symbolication
jbachorik Jan 16, 2026
bb6ea57
Update REMOTE_SYMBOLICATION.md to reflect current implementation
jbachorik Jan 16, 2026
8b80bf1
Move docs/architecture to doc/architecture for consistency
jbachorik Jan 16, 2026
845a81a
Update documentation references to doc/architecture path
jbachorik Jan 16, 2026
05dedd5
Standardize doc/ file naming to PascalCase
jbachorik Jan 16, 2026
d180ee3
Make remotesym argument parsing robust and consistent
jbachorik Jan 16, 2026
f958fb0
Fix integer overflow vulnerability and test race condition
jbachorik Jan 16, 2026
b131a69
Fix format string portability for uintptr_t
jbachorik Jan 16, 2026
5a6c63d
Skip RemoteSymbolicationTest on OpenJ9 due to JVMTI limitation
jbachorik Jan 16, 2026
527aba3
Update copyrights to 2026 on modified files
jbachorik Jan 16, 2026
8164943
Add comprehensive build-id specification references to symbols_linux_…
jbachorik Jan 16, 2026
255201d
Fix remote symbolication NativeFunc::mark() type confusion
jbachorik Jan 15, 2026
64b356e
Dedupe shared code
jbachorik Jan 19, 2026
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
2 changes: 1 addition & 1 deletion .claude/commands/build-and-summarize
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ echo
echo "Delegating to gradle-logs-analyst agent…"
# If your CLI supports non-streaming, set it here to avoid verbose output.
# Example (uncomment if supported): export CLAUDE_NO_STREAM=1
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."
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."
12 changes: 8 additions & 4 deletions .claude/commands/build-and-summarize.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# build-and-summarize

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.
Execute the bash script `~/.claude/commands/build-and-summarize` with all provided arguments.

## Usage
```bash
./.claude/commands/build-and-summarize [<gradle-args>...]
This script will:
- Run `./gradlew` with the specified arguments (defaults to 'build' if none provided)
- Capture full output to a timestamped log in `build/logs/`
- Show minimal live progress in the console
- Delegate to the `gradle-logs-analyst` agent for structured analysis

Pass through all arguments exactly as provided by the user.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,25 @@ Improved thread-local storage initialization to prevent race conditions:

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

### Remote Symbolication Support (2025)

Added support for remote symbolication to enable offloading symbol resolution from the agent to backend services:

- **Build-ID extraction**: Automatically extracts GNU build-id from ELF binaries on Linux
- **Raw addressing information**: Stores build-id and PC offset instead of resolved symbol names
- **Remote symbolication mode**: Enable with `remotesym=true` profiler argument
- **JFR integration**: Remote frames serialized with build-id and offset for backend resolution
- **Zero encoding overhead**: Uses dedicated frame type (FRAME_NATIVE_REMOTE) for efficient serialization

**Benefits**:
- Reduces agent overhead by eliminating local symbol resolution
- Enables centralized symbol resolution with better caching
- Supports scenarios where debug symbols are not available locally

**Key files**: `elfBuildId.h`, `elfBuildId.cpp`, `profiler.cpp`, `flightRecorder.cpp`

For detailed documentation, see [doc/RemoteSymbolication.md](doc/RemoteSymbolication.md).

## Contributing
1. Fork the repository
2. Create a feature branch
Expand Down
35 changes: 29 additions & 6 deletions ddprof-lib/src/main/cpp/arguments.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright 2017 Andrei Pangin
* Copyright 2026, Datadog, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -88,7 +89,10 @@ static const Multiplier UNIVERSAL[] = {
// samples
// generations - track surviving generations
// lightweight[=BOOL] - enable lightweight profiling - events without
// stacktraces (default: true) jfr - dump events in Java
// stacktraces (default: true)
// remotesym[=BOOL] - enable remote symbolication for native frames
// (stores build-id and PC offset instead of symbol names)
// jfr - dump events in Java
// Flight Recorder format interval=N - sampling interval in ns
// (default: 10'000'000, i.e. 10 ms) jstackdepth=N - maximum Java stack
// depth (default: 2048) safemode=BITS - disable stack recovery
Expand Down Expand Up @@ -339,16 +343,35 @@ Error Arguments::parse(const char *args) {
_enable_method_cleanup = true;
}

CASE("wallsampler")
CASE("remotesym")
if (value != NULL) {
switch (value[0]) {
case 'j':
_wallclock_sampler = JVMTI;
case 'n': // no
case 'f': // false
case '0': // 0
_remote_symbolication = false;
break;
case 'a':
case 'y': // yes
case 't': // true
case '1': // 1
default:
_wallclock_sampler = ASGCT;
_remote_symbolication = true;
}
} else {
// No value means enable
_remote_symbolication = true;
}

CASE("wallsampler")
if (value != NULL) {
switch (value[0]) {
case 'j':
_wallclock_sampler = JVMTI;
break;
case 'a':
default:
_wallclock_sampler = ASGCT;
}
}

DEFAULT()
Expand Down
4 changes: 3 additions & 1 deletion ddprof-lib/src/main/cpp/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ class Arguments {
std::vector<std::string> _context_attributes;
bool _lightweight;
bool _enable_method_cleanup;
bool _remote_symbolication; // Enable remote symbolication for native frames

Arguments(bool persistent = false)
: _buf(NULL),
Expand Down Expand Up @@ -221,7 +222,8 @@ class Arguments {
_context_attributes({}),
_wallclock_sampler(ASGCT),
_lightweight(false),
_enable_method_cleanup(true) {}
_enable_method_cleanup(true),
_remote_symbolication(false) {}

~Arguments();

Expand Down
86 changes: 58 additions & 28 deletions ddprof-lib/src/main/cpp/codeCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ CodeCache::CodeCache(const char *name, short lib_index,
_plt_size = 0;
_debug_symbols = false;

// Initialize build-id fields
_build_id = nullptr;
_build_id_len = 0;
_load_bias = 0;

memset(_imports, 0, sizeof(_imports));
_imports_patchable = imports_patchable;

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

CodeCache::CodeCache(const CodeCache &other) {
void CodeCache::copyFrom(const CodeCache& other) {
_name = NativeFunc::create(other._name, -1);
_lib_index = other._lib_index;
_min_address = other._min_address;
_max_address = other._max_address;
_text_base = other._text_base;
_image_base = other._image_base;

_imports_patchable = other._imports_patchable;
_plt_offset = other._plt_offset;
_plt_size = other._plt_size;
_debug_symbols = other._debug_symbols;

// Copy build-id information
_build_id_len = other._build_id_len;
if (other._build_id != nullptr && other._build_id_len > 0) {
size_t hex_str_len = strlen(other._build_id);
_build_id = static_cast<char*>(malloc(hex_str_len + 1));
if (_build_id != nullptr) {
strcpy(_build_id, other._build_id);
}
} else {
_build_id = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

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

That assignment is redundant, b/c build_id is initialized with nullptr. You could save the whole else-branch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah. This is definitely not needed.

}
_load_bias = other._load_bias;

memset(_imports, 0, sizeof(_imports));
_imports_patchable = other._imports_patchable;

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

CodeCache::CodeCache(const CodeCache &other) {
copyFrom(other);
}

CodeCache &CodeCache::operator=(const CodeCache &other) {
if (&other == this) {
return *this;
} else {
delete _name;
delete _dwarf_table;
delete _blobs;

_name = NativeFunc::create(other._name, -1);
_lib_index = other._lib_index;
_min_address = other._min_address;
_max_address = other._max_address;
_text_base = other._text_base;

_imports_patchable = other._imports_patchable;

_plt_offset = other._plt_offset;
_plt_size = other._plt_size;
}

_dwarf_table_length = other._dwarf_table_length;
_dwarf_table = new FrameDesc[_dwarf_table_length];
memcpy(_dwarf_table, other._dwarf_table,
_dwarf_table_length * sizeof(FrameDesc));
NativeFunc::destroy(_name);
delete[] _dwarf_table;
delete[] _blobs;
free(_build_id);

_capacity = other._capacity;
_count = other._count;
_blobs = new CodeBlob[_capacity];
memcpy(_blobs, other._blobs, _count * sizeof(CodeBlob));
copyFrom(other);

return *this;
}
return *this;
}

CodeCache::~CodeCache() {
Expand All @@ -109,7 +117,8 @@ CodeCache::~CodeCache() {
}
NativeFunc::destroy(_name);
delete[] _blobs;
delete _dwarf_table;
delete[] _dwarf_table;
free(_build_id); // Free build-id memory
}

void CodeCache::expand() {
Expand Down Expand Up @@ -387,3 +396,24 @@ FrameDesc CodeCache::findFrameDesc(const void *pc) {
return FrameDesc::default_frame;
}
}

void CodeCache::setBuildId(const char* build_id, size_t build_id_len) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Somehow I have the feeling here comes the same sequence of code, yet again (3rd time). Can this be unified (maybe the other 2 instances can be made to call this method)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Created a shared function to copy the relevant parts.

// Free existing build-id if any
free(_build_id);
_build_id = nullptr;
_build_id_len = 0;

if (build_id != nullptr && build_id_len > 0) {
// build_id is a hex string, allocate based on actual string length
size_t hex_str_len = strlen(build_id);
_build_id = static_cast<char*>(malloc(hex_str_len + 1));

if (_build_id != nullptr) {
// Copy the hex string
strcpy(_build_id, build_id);
// Store the original byte length (not hex string length)
_build_id_len = build_id_len;
}
}
}

30 changes: 28 additions & 2 deletions ddprof-lib/src/main/cpp/codeCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ class CodeCache {
unsigned int _plt_offset;
unsigned int _plt_size;

// Build-ID and load bias for remote symbolication
char *_build_id; // GNU build-id (hex string, null if not available)
size_t _build_id_len; // Build-id length in bytes (raw, not hex string length)
uintptr_t _load_bias; // Load bias (image_base - file_base address)

void **_imports[NUM_IMPORTS][NUM_IMPORT_TYPES];
bool _imports_patchable;
bool _debug_symbols;
Expand All @@ -130,6 +135,7 @@ class CodeCache {
void expand();
void makeImportsPatchable();
void saveImport(ImportId id, void** entry);
void copyFrom(const CodeCache& other);

public:
explicit CodeCache(const char *name, short lib_index = -1,
Expand Down Expand Up @@ -169,10 +175,30 @@ class CodeCache {

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

// Build-ID and remote symbolication support
const char* buildId() const { return _build_id; }
size_t buildIdLen() const { return _build_id_len; }
bool hasBuildId() const { return _build_id != nullptr; }
uintptr_t loadBias() const { return _load_bias; }
short libIndex() const { return _lib_index; }

// Sets the build-id (hex string) and stores the original byte length
// build_id: null-terminated hex string (e.g., "abc123..." for 40-char string)
// build_id_len: original byte length before hex conversion (e.g., 20 bytes)
void setBuildId(const char* build_id, size_t build_id_len);
void setLoadBias(uintptr_t load_bias) { _load_bias = load_bias; }

void add(const void *start, int length, const char *name,
bool update_bounds = false);
void updateBounds(const void *start, const void *end);
void sort();

/**
* Mark symbols matching the predicate with the given mark value.
*
* This is called during profiler initialization to mark JVM internal functions
* (MARK_VM_RUNTIME, MARK_INTERPRETER, MARK_COMPILER_ENTRY, MARK_ASYNC_PROFILER).
*/
template <typename NamePredicate>
inline void mark(NamePredicate predicate, char value) {
for (int i = 0; i < _count; i++) {
Expand Down Expand Up @@ -225,7 +251,7 @@ class CodeCacheArray {
memset(_libs, 0, MAX_NATIVE_LIBS * sizeof(CodeCache *));
}

CodeCache *operator[](int index) { return _libs[index]; }
CodeCache *operator[](int index) const { return __atomic_load_n(&_libs[index], __ATOMIC_ACQUIRE); }

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

Expand All @@ -247,7 +273,7 @@ class CodeCacheArray {
return lib;
}

size_t memoryUsage() {
size_t memoryUsage() const {
return __atomic_load_n(&_used_memory, __ATOMIC_RELAXED);
}
};
Expand Down
5 changes: 4 additions & 1 deletion ddprof-lib/src/main/cpp/counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@
X(UNWINDING_TIME_ASYNC, "unwinding_ticks_async") \
X(UNWINDING_TIME_JVMTI, "unwinding_ticks_jvmti") \
X(CALLTRACE_STORAGE_DROPPED, "calltrace_storage_dropped_traces") \
X(LINE_NUMBER_TABLES, "line_number_tables")
X(LINE_NUMBER_TABLES, "line_number_tables") \
X(REMOTE_SYMBOLICATION_FRAMES, "remote_symbolication_frames") \
X(REMOTE_SYMBOLICATION_LIBS_WITH_BUILD_ID, "remote_symbolication_libs_with_build_id") \
X(REMOTE_SYMBOLICATION_BUILD_ID_CACHE_HITS, "remote_symbolication_build_id_cache_hits")
#define X_ENUM(a, b) a,
typedef enum CounterId : int {
DD_COUNTER_TABLE(X_ENUM) DD_NUM_COUNTERS
Expand Down
Loading
Loading