workQueue = Queues.spscArrayQueue(Short.MAX_VALUE);
+
+ /** Signals when collection is complete. */
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ /**
+ * Consumer thread that processes the work queue and extracts build IDs.
+ *
+ * Threading: Runs in a single background thread. Only one instance is ever created,
+ * guaranteed by the {@code collecting.compareAndSet(false, true)} check in {@link
+ * #resolveBuildId(Path)}.
+ *
+ *
Polls the {@code workQueue} until either:
+ *
+ *
+ * - The deadline is reached, or
+ *
- The {@code collecting} flag is set to false (via {@link #awaitCollectionDone(int)}) and
+ * the queue is empty
+ *
+ */
+ class Collector implements Runnable {
+ private final BuildIdExtractor extractor = BuildIdExtractor.create();
+ private final long deadline;
+
+ Collector(long timeout, TimeUnit unit) {
+ this.deadline = unit.toNanos(timeout) + System.nanoTime();
+ }
+
+ @Override
+ public void run() {
+ while (System.nanoTime() <= deadline) {
+ final Path path = workQueue.poll();
+ if (path == null) {
+ if (!collecting.get()) {
+ break;
+ }
+ LockSupport.parkNanos(MILLISECONDS.toNanos(50));
+ continue;
+ }
+ final String fileName = path.getFileName().toString();
+ LOGGER.debug("Resolving build id for {} against {}", fileName, path);
+ final String buildId = extractor.extractBuildId(path);
+ if (buildId != null) {
+ LOGGER.debug("Found build id {} for library {}", buildId, fileName);
+ libraryBuildInfo.put(
+ fileName, new BuildInfo(buildId, extractor.buildIdType(), extractor.fileType()));
+ }
+ }
+ latch.countDown();
+ }
+ }
+
+ /**
+ * Registers a library filename as needing build ID resolution.
+ *
+ * Called from producer thread (crash parsing flow) before collection starts.
+ *
+ * @param filename the library filename to track
+ */
+ public void addUnprocessedLibrary(String filename) {
+ if (!collecting.get()) {
+ libraryBuildInfo.putIfAbsent(filename, EMPTY);
+ }
+ }
+
+ /**
+ * Enqueues a library path for build ID extraction.
+ *
+ *
Threading: This method is called exclusively from the producer thread (crash parsing
+ * flow). It starts the collector thread on first invocation and enqueues work items.
+ *
+ *
The {@code processed} set is only accessed here (producer thread), so no synchronization is
+ * needed for it.
+ *
+ * @param path the path to the library file
+ */
+ public void resolveBuildId(Path path) {
+ if (collecting.compareAndSet(false, true)) {
+ AgentTaskScheduler.get().execute(new Collector(5, SECONDS));
+ }
+ final String filename = path.getFileName().toString();
+ if (!processed.add(filename)) {
+ return;
+ }
+ if (libraryBuildInfo.remove(filename) == null) {
+ // the library is not present in the collected ones part of the stackframe
+ LOGGER.debug(
+ "Skipping build id resolution for {} as it was not added to unprocessed", filename);
+
+ } else if (!workQueue.offer(path)) {
+ LOGGER.warn(
+ "Could not resolve the build id for library {} because the processing queue is full",
+ path);
+ }
+ }
+
+ /**
+ * Signals that no more work will be enqueued and waits for collection to complete.
+ *
+ *
Called from producer thread to stop collection and wait for the collector to finish
+ * processing the queue.
+ *
+ * @param timeoutSeconds maximum time to wait for collection to complete
+ */
+ public void awaitCollectionDone(final int timeoutSeconds) {
+ if (!collecting.compareAndSet(true, false)) {
+ return;
+ }
+ try {
+ if (!latch.await(timeoutSeconds, SECONDS)) {
+ LOGGER.warn("Build id collection incomplete.");
+ }
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ LOGGER.warn("Interrupted while waiting for build id collection to finish");
+ }
+ }
+
+ /**
+ * Retrieves the build information for a library.
+ *
+ *
This method can be called from any thread after collection is complete. The {@link
+ * ConcurrentHashMap} ensures thread-safe reads.
+ *
+ * @param filename the library filename
+ * @return the build information, or null if not found
+ */
+ public BuildInfo getBuildInfo(String filename) {
+ return libraryBuildInfo.get(filename);
+ }
+}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/BuildIdExtractor.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/BuildIdExtractor.java
new file mode 100644
index 00000000000..859a62b10f9
--- /dev/null
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/BuildIdExtractor.java
@@ -0,0 +1,42 @@
+package datadog.crashtracking.buildid;
+
+import datadog.environment.OperatingSystem;
+import java.nio.file.Path;
+
+/**
+ * Interface for extracting build IDs from native library binaries. Build IDs help identify exact
+ * library versions for symbolization of native stack traces.
+ */
+public interface BuildIdExtractor {
+ /**
+ * Extracts build ID from a binary file.
+ *
+ * @param file Path to the library file
+ * @return Build ID as hex string, or null if not found or on error
+ */
+ String extractBuildId(Path file);
+
+ /**
+ * @return the file type this extractor operates for.
+ */
+ BuildInfo.FileType fileType();
+
+ /**
+ * @return the build id type this extractor is able to provide.
+ */
+ BuildInfo.BuildIdType buildIdType();
+
+ /**
+ * Factory method that returns appropriate extractor for the platform.
+ *
+ * @return Platform-specific build ID extractor
+ */
+ static BuildIdExtractor create() {
+ if (OperatingSystem.isLinux()) {
+ return new ElfBuildIdExtractor();
+ } else if (OperatingSystem.isWindows()) {
+ return new PeBuildIdExtractor();
+ }
+ return new NoOpBuildIdExtractor();
+ }
+}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/BuildInfo.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/BuildInfo.java
new file mode 100644
index 00000000000..fe4ffd41477
--- /dev/null
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/BuildInfo.java
@@ -0,0 +1,25 @@
+package datadog.crashtracking.buildid;
+
+public class BuildInfo {
+ public enum BuildIdType {
+ GNU, // for ELF
+ PDB // for DLL PE
+ }
+
+ public enum FileType {
+ ELF,
+ PE,
+ }
+
+ static final BuildInfo EMPTY = new BuildInfo(null, null, null);
+
+ public final String buildId;
+ public final BuildIdType buildIdType;
+ public final FileType fileType;
+
+ public BuildInfo(final String buildId, final BuildIdType buildIdType, final FileType fileType) {
+ this.buildId = buildId;
+ this.buildIdType = buildIdType;
+ this.fileType = fileType;
+ }
+}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/ElfBuildIdExtractor.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/ElfBuildIdExtractor.java
new file mode 100644
index 00000000000..3439c1db5cf
--- /dev/null
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/ElfBuildIdExtractor.java
@@ -0,0 +1,212 @@
+package datadog.crashtracking.buildid;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Path;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extracts build IDs from ELF (Executable and Linkable Format) binaries. Supports both 32-bit and
+ * 64-bit ELF files with little-endian and big-endian byte ordering.
+ */
+public class ElfBuildIdExtractor implements BuildIdExtractor {
+ private static final Logger log = LoggerFactory.getLogger(ElfBuildIdExtractor.class);
+
+ // ELF magic: 0x7f 'E' 'L' 'F'
+ private static final byte[] ELF_MAGIC = {0x7f, 0x45, 0x4c, 0x46};
+
+ // ELF header constants
+ private static final int ELFCLASS32 = 1;
+ private static final int ELFCLASS64 = 2;
+
+ private static final int ELFDATA2LSB = 1; // Little endian
+ private static final int ELFDATA2MSB = 2; // Big endian
+
+ // Program header constants
+ private static final int PT_NOTE = 4;
+
+ // Note header constants
+ private static final int NT_GNU_BUILD_ID = 3;
+ private static final byte[] GNU_NOTE_NAME = "GNU\0".getBytes();
+
+ @Override
+ public String extractBuildId(Path file) {
+ try (RandomAccessFile raf = new RandomAccessFile(file.toFile(), "r")) {
+ // 1. Read and verify ELF magic (first 4 bytes)
+ byte[] magic = new byte[4];
+ if (raf.read(magic) != 4 || !Arrays.equals(magic, ELF_MAGIC)) {
+ log.debug("Not an ELF file: {}", file);
+ return null;
+ }
+
+ // 2. Determine file class (32 or 64 bit)
+ int elfClass = raf.read();
+ boolean is64Bit = (elfClass == ELFCLASS64);
+ if (elfClass != ELFCLASS32 && elfClass != ELFCLASS64) {
+ log.debug("Invalid ELF class in file: {}", file);
+ return null;
+ }
+
+ // 3. Determine endianness
+ int elfData = raf.read();
+ boolean isLittleEndian = (elfData == ELFDATA2LSB);
+ if (elfData != ELFDATA2LSB && elfData != ELFDATA2MSB) {
+ log.debug("Invalid ELF data encoding in file: {}", file);
+ return null;
+ }
+
+ // 4. Read ELF header to get program header table offset and size
+ raf.seek(is64Bit ? 32 : 28); // Offset to e_phoff
+ long phoff = is64Bit ? readLong(raf, isLittleEndian) : readInt(raf, isLittleEndian);
+
+ raf.seek(is64Bit ? 54 : 42); // Offset to e_phnum
+ int phnum = readShort(raf, isLittleEndian);
+
+ // 5. Iterate through program headers to find PT_NOTE segments
+ for (int i = 0; i < phnum; i++) {
+ long phEntryOffset = phoff + ((long) i * (is64Bit ? 56 : 32));
+ raf.seek(phEntryOffset);
+
+ int pType = (int) readInt(raf, isLittleEndian);
+ if (pType != PT_NOTE) {
+ continue;
+ }
+
+ // Read PT_NOTE segment offset and size
+ long pOffset, pSize;
+ if (is64Bit) {
+ raf.seek(phEntryOffset + 8);
+ pOffset = readLong(raf, isLittleEndian);
+ raf.seek(phEntryOffset + 32);
+ pSize = readLong(raf, isLittleEndian);
+ } else {
+ raf.seek(phEntryOffset + 4);
+ pOffset = readInt(raf, isLittleEndian);
+ raf.seek(phEntryOffset + 16);
+ pSize = readInt(raf, isLittleEndian);
+ }
+
+ // 6. Parse notes in this PT_NOTE segment
+ String buildId = parseNoteSegment(raf, pOffset, pSize, isLittleEndian);
+ if (buildId != null) {
+ return buildId;
+ }
+ }
+
+ log.debug("No build ID found in ELF file: {}", file);
+ return null;
+
+ } catch (IOException | SecurityException e) {
+ log.debug("Failed to extract ELF build ID from {}: {}", file, e.getMessage());
+ return null;
+ } catch (Throwable t) {
+ log.debug("Unexpected error extracting ELF build ID from {}: {}", file, t.getMessage());
+ return null;
+ }
+ }
+
+ @Override
+ public BuildInfo.FileType fileType() {
+ return BuildInfo.FileType.ELF;
+ }
+
+ @Override
+ public BuildInfo.BuildIdType buildIdType() {
+ return BuildInfo.BuildIdType.GNU;
+ }
+
+ private String parseNoteSegment(
+ RandomAccessFile raf, long offset, long size, boolean isLittleEndian) throws IOException {
+ raf.seek(offset);
+ long end = offset + size;
+
+ while (raf.getFilePointer() < end) {
+ long currentPos = raf.getFilePointer();
+
+ // Ensure we don't read beyond segment
+ if (currentPos + 12 > end) {
+ break;
+ }
+
+ int namesz = (int) readInt(raf, isLittleEndian);
+ int descsz = (int) readInt(raf, isLittleEndian);
+ int type = (int) readInt(raf, isLittleEndian);
+
+ // Align to 4-byte boundary
+ int nameLen = (namesz + 3) & ~3;
+ int descLen = (descsz + 3) & ~3;
+
+ // Bounds check
+ if (currentPos + 12 + nameLen + descLen > end) {
+ break;
+ }
+
+ // Read note name
+ byte[] name = new byte[namesz];
+ if (raf.read(name) != namesz) {
+ throw new IOException("Failed to read note name");
+ }
+ int skipped = raf.skipBytes(nameLen - namesz);
+ if (skipped != nameLen - namesz) {
+ throw new IOException("Failed to skip padding after note name");
+ }
+
+ // Check if this is the GNU build ID note
+ if (type == NT_GNU_BUILD_ID && Arrays.equals(name, GNU_NOTE_NAME)) {
+ // Read build ID
+ byte[] buildIdBytes = new byte[descsz];
+ if (raf.read(buildIdBytes) != descsz) {
+ throw new IOException("Failed to read build ID");
+ }
+
+ // Convert to hex string
+ StringBuilder hex = new StringBuilder(descsz * 2);
+ for (byte b : buildIdBytes) {
+ hex.append(String.format("%02x", b & 0xff));
+ }
+ return hex.toString();
+ } else {
+ // Skip descriptor
+ skipped = raf.skipBytes(descLen);
+ if (skipped != descLen) {
+ throw new IOException("Failed to skip descriptor");
+ }
+ }
+ }
+ return null;
+ }
+
+ private int readShort(RandomAccessFile raf, boolean isLittleEndian) throws IOException {
+ byte[] bytes = new byte[2];
+ if (raf.read(bytes) != 2) {
+ throw new IOException("Failed to read short");
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes);
+ buf.order(isLittleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
+ return buf.getShort() & 0xFFFF;
+ }
+
+ private long readInt(RandomAccessFile raf, boolean isLittleEndian) throws IOException {
+ byte[] bytes = new byte[4];
+ if (raf.read(bytes) != 4) {
+ throw new IOException("Failed to read int");
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes);
+ buf.order(isLittleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
+ return buf.getInt() & 0xFFFFFFFFL;
+ }
+
+ private long readLong(RandomAccessFile raf, boolean isLittleEndian) throws IOException {
+ byte[] bytes = new byte[8];
+ if (raf.read(bytes) != 8) {
+ throw new IOException("Failed to read long");
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes);
+ buf.order(isLittleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
+ return buf.getLong();
+ }
+}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/NoOpBuildIdExtractor.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/NoOpBuildIdExtractor.java
new file mode 100644
index 00000000000..71bf8e8c31f
--- /dev/null
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/NoOpBuildIdExtractor.java
@@ -0,0 +1,24 @@
+package datadog.crashtracking.buildid;
+
+import java.nio.file.Path;
+
+/**
+ * No-op build ID extractor for unsupported platforms. Always returns null to indicate build IDs are
+ * not available.
+ */
+public class NoOpBuildIdExtractor implements BuildIdExtractor {
+ @Override
+ public String extractBuildId(Path file) {
+ return null; // No build ID on this platform
+ }
+
+ @Override
+ public BuildInfo.FileType fileType() {
+ return null;
+ }
+
+ @Override
+ public BuildInfo.BuildIdType buildIdType() {
+ return null;
+ }
+}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/PeBuildIdExtractor.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/PeBuildIdExtractor.java
new file mode 100644
index 00000000000..19a7afed4d5
--- /dev/null
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/PeBuildIdExtractor.java
@@ -0,0 +1,314 @@
+package datadog.crashtracking.buildid;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Path;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extracts build IDs from PE (Portable Executable) binaries used on Windows. Uses the GUID and Age
+ * from the PDB70 CodeView debug information.
+ */
+public class PeBuildIdExtractor implements BuildIdExtractor {
+ private static final Logger log = LoggerFactory.getLogger(PeBuildIdExtractor.class);
+
+ // DOS header magic: 'M' 'Z'
+ private static final byte[] MZ_MAGIC = {0x4d, 0x5a};
+
+ // PE signature: 'P' 'E' 0x00 0x00
+ private static final byte[] PE_SIGNATURE = {0x50, 0x45, 0x00, 0x00};
+
+ // PDB70 CodeView signature: 'R' 'S' 'D' 'S'
+ private static final int PDB70_SIGNATURE = 0x53445352;
+
+ // Debug directory type for CodeView
+ private static final int IMAGE_DEBUG_TYPE_CODEVIEW = 2;
+
+ // PE32 magic
+ private static final short PE32_MAGIC = 0x10b;
+
+ // PE32+ (64-bit) magic
+ private static final short PE32_PLUS_MAGIC = 0x20b;
+
+ @Override
+ public String extractBuildId(Path file) {
+ try (RandomAccessFile raf = new RandomAccessFile(file.toFile(), "r")) {
+ long size = raf.length();
+
+ // 1. Verify DOS header magic (first 2 bytes)
+ byte[] magic = new byte[2];
+ if (raf.read(magic) != 2 || !Arrays.equals(magic, MZ_MAGIC)) {
+ log.debug("Not a PE file (missing MZ magic): {}", file);
+ return null;
+ }
+
+ // 2. Read PE header offset from DOS header (at offset 0x3C)
+ raf.seek(0x3C);
+ byte[] offsetBytes = new byte[4];
+ if (raf.read(offsetBytes) != 4) {
+ log.debug("Failed to read PE header offset from: {}", file);
+ return null;
+ }
+ ByteBuffer buf = ByteBuffer.wrap(offsetBytes);
+ buf.order(ByteOrder.LITTLE_ENDIAN); // PE is always little-endian
+ long peOffset = buf.getInt() & 0xFFFFFFFFL;
+
+ // Bounds check
+ if (peOffset > size - 4) {
+ log.debug("Invalid PE header offset in file: {}", file);
+ return null;
+ }
+
+ // 3. Verify PE signature
+ raf.seek(peOffset);
+ byte[] peSig = new byte[4];
+ if (raf.read(peSig) != 4 || !Arrays.equals(peSig, PE_SIGNATURE)) {
+ log.debug("Invalid PE signature in file: {}", file);
+ return null;
+ }
+
+ // 4. Read COFF header
+ // COFF header starts right after PE signature
+ // Layout:
+ // +0: Machine (2 bytes)
+ // +2: NumberOfSections (2 bytes)
+ // +4: TimeDateStamp (4 bytes)
+ // +8: PointerToSymbolTable (4 bytes)
+ // +12: NumberOfSymbols (4 bytes)
+ // +16: SizeOfOptionalHeader (2 bytes)
+ // +18: Characteristics (2 bytes)
+ long coffHeaderOffset = peOffset + 4;
+
+ if (coffHeaderOffset + 20 > size) {
+ log.debug("Invalid COFF header position in file: {}", file);
+ return null;
+ }
+
+ raf.seek(coffHeaderOffset + 16);
+ byte[] sizeBytes = new byte[2];
+ if (raf.read(sizeBytes) != 2) {
+ log.debug("Failed to read SizeOfOptionalHeader from: {}", file);
+ return null;
+ }
+ buf = ByteBuffer.wrap(sizeBytes);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ int sizeOfOptionalHeader = buf.getShort() & 0xFFFF;
+
+ if (sizeOfOptionalHeader == 0) {
+ log.debug("No optional header in PE file: {}", file);
+ return null;
+ }
+
+ // 5. Read Optional Header to get Debug Directory RVA and size
+ // Optional header starts right after COFF header (20 bytes after PE signature)
+ long optionalHeaderOffset = coffHeaderOffset + 20;
+
+ if (optionalHeaderOffset + 2 > size) {
+ log.debug("Invalid optional header position in file: {}", file);
+ return null;
+ }
+
+ raf.seek(optionalHeaderOffset);
+ byte[] magicBytes = new byte[2];
+ if (raf.read(magicBytes) != 2) {
+ log.debug("Failed to read optional header magic from: {}", file);
+ return null;
+ }
+ buf = ByteBuffer.wrap(magicBytes);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ short optionalMagic = buf.getShort();
+
+ boolean is64Bit = (optionalMagic == PE32_PLUS_MAGIC);
+
+ // Debug directory is in the data directories array
+ // For PE32: offset 96 from optional header start
+ // For PE32+: offset 112 from optional header start
+ long debugDirOffset = optionalHeaderOffset + (is64Bit ? 112 : 96) + (6 * 8); // 6th entry
+ // Each data directory entry is 8 bytes: RVA (4) + Size (4)
+
+ if (debugDirOffset + 8 > size) {
+ log.debug("Invalid debug directory offset in file: {}", file);
+ return null;
+ }
+
+ raf.seek(debugDirOffset);
+ byte[] debugDirData = new byte[8];
+ if (raf.read(debugDirData) != 8) {
+ log.debug("Failed to read debug directory data from: {}", file);
+ return null;
+ }
+ buf = ByteBuffer.wrap(debugDirData);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ long debugDirRva = buf.getInt() & 0xFFFFFFFFL;
+ long debugDirSize = buf.getInt() & 0xFFFFFFFFL;
+
+ if (debugDirRva == 0 || debugDirSize == 0) {
+ log.debug("No debug directory in PE file: {}", file);
+ return null;
+ }
+
+ // 6. Convert RVA to file offset by reading section headers
+ long sectionHeadersOffset = optionalHeaderOffset + sizeOfOptionalHeader;
+ long debugDirFileOffset =
+ rvaToFileOffset(raf, sectionHeadersOffset, coffHeaderOffset, debugDirRva, size);
+
+ if (debugDirFileOffset == -1) {
+ log.debug("Failed to convert debug directory RVA to file offset: {}", file);
+ return null;
+ }
+
+ // 7. Parse debug directory entries to find CodeView entry
+ int numEntries = (int) (debugDirSize / 28); // Each debug directory entry is 28 bytes
+ for (int i = 0; i < numEntries; i++) {
+ long entryOffset = debugDirFileOffset + (i * 28);
+ if (entryOffset + 28 > size) {
+ continue;
+ }
+
+ raf.seek(entryOffset);
+ byte[] entryData = new byte[28];
+ if (raf.read(entryData) != 28) {
+ continue;
+ }
+
+ buf = ByteBuffer.wrap(entryData);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+
+ buf.getInt(); // Characteristics
+ buf.getInt(); // TimeDateStamp
+ buf.getShort(); // MajorVersion
+ buf.getShort(); // MinorVersion
+ int type = buf.getInt();
+ int dataSize = buf.getInt();
+ long dataRva = buf.getInt() & 0xFFFFFFFFL;
+ long dataFilePointer = buf.getInt() & 0xFFFFFFFFL;
+
+ if (type == IMAGE_DEBUG_TYPE_CODEVIEW && dataSize > 0) {
+ // Found CodeView entry, read PDB70 structure
+ if (dataFilePointer + dataSize > size) {
+ log.debug("Invalid CodeView data pointer in file: {}", file);
+ continue;
+ }
+
+ raf.seek(dataFilePointer);
+ byte[] cvData = new byte[Math.min(dataSize, 24)]; // PDB70 header is 24 bytes
+ if (raf.read(cvData) != cvData.length) {
+ continue;
+ }
+
+ buf = ByteBuffer.wrap(cvData);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+
+ int signature = buf.getInt();
+ if (signature != PDB70_SIGNATURE) {
+ continue;
+ }
+
+ // Read GUID (16 bytes)
+ long guidData1 = buf.getInt() & 0xFFFFFFFFL;
+ int guidData2 = buf.getShort() & 0xFFFF;
+ int guidData3 = buf.getShort() & 0xFFFF;
+ byte[] guidData4 = new byte[8];
+ buf.get(guidData4);
+
+ // Read Age (4 bytes)
+ long age = buf.getInt() & 0xFFFFFFFFL;
+
+ // Format as GUID + Age in hex (like dotnet tracer)
+ return formatBuildId(guidData1, guidData2, guidData3, guidData4, age);
+ }
+ }
+
+ log.debug("No CodeView debug information found in PE file: {}", file);
+ return null;
+
+ } catch (IOException | SecurityException e) {
+ log.debug("Failed to extract PE build ID from {}: {}", file, e.getMessage());
+ return null;
+ } catch (Throwable t) {
+ log.debug("Unexpected error extracting PE build ID from {}: {}", file, t.getMessage());
+ return null;
+ }
+ }
+
+ private long rvaToFileOffset(
+ RandomAccessFile raf,
+ long sectionHeadersOffset,
+ long coffHeaderOffset,
+ long rva,
+ long fileSize)
+ throws IOException {
+ // Read number of sections from COFF header
+ raf.seek(coffHeaderOffset + 2);
+ byte[] numSectionsBytes = new byte[2];
+ if (raf.read(numSectionsBytes) != 2) {
+ return -1;
+ }
+ ByteBuffer buf = ByteBuffer.wrap(numSectionsBytes);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ int numSections = buf.getShort() & 0xFFFF;
+
+ // Each section header is 40 bytes
+ for (int i = 0; i < numSections; i++) {
+ long sectionOffset = sectionHeadersOffset + (i * 40);
+ if (sectionOffset + 40 > fileSize) {
+ continue;
+ }
+
+ raf.seek(sectionOffset + 8); // Skip name (8 bytes)
+ byte[] sectionData = new byte[16];
+ if (raf.read(sectionData) != 16) {
+ continue;
+ }
+
+ buf = ByteBuffer.wrap(sectionData);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+
+ long virtualSize = buf.getInt() & 0xFFFFFFFFL;
+ long virtualAddress = buf.getInt() & 0xFFFFFFFFL;
+ @SuppressWarnings("unused")
+ long sizeOfRawData = buf.getInt() & 0xFFFFFFFFL;
+ long pointerToRawData = buf.getInt() & 0xFFFFFFFFL;
+
+ // Check if RVA falls within this section
+ if (rva >= virtualAddress && rva < virtualAddress + virtualSize) {
+ return pointerToRawData + (rva - virtualAddress);
+ }
+ }
+
+ return -1;
+ }
+
+ private String formatBuildId(
+ long guidData1, int guidData2, int guidData3, byte[] guidData4, long age) {
+ // Format: GUID (uppercase, without dashes) + Age (lowercase hex)
+ return String.format(
+ "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x",
+ guidData1,
+ guidData2,
+ guidData3,
+ guidData4[0] & 0xFF,
+ guidData4[1] & 0xFF,
+ guidData4[2] & 0xFF,
+ guidData4[3] & 0xFF,
+ guidData4[4] & 0xFF,
+ guidData4[5] & 0xFF,
+ guidData4[6] & 0xFF,
+ guidData4[7] & 0xFF,
+ age);
+ }
+
+ @Override
+ public BuildInfo.FileType fileType() {
+ return BuildInfo.FileType.PE;
+ }
+
+ @Override
+ public BuildInfo.BuildIdType buildIdType() {
+ return BuildInfo.BuildIdType.PDB;
+ }
+}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/CrashLog.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/CrashLog.java
index aa3a672ac9c..cd339934782 100644
--- a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/CrashLog.java
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/CrashLog.java
@@ -10,7 +10,7 @@
public final class CrashLog {
private static final int VERSION = 0;
- private static final JsonAdapter ADAPTER;
+ public static final JsonAdapter ADAPTER;
static {
Moshi moshi = new Moshi.Builder().add(new SemanticVersion.SemanticVersionAdapter()).build();
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/ErrorData.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/ErrorData.java
index 9ccb9a96e0d..5e94a500e6f 100644
--- a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/ErrorData.java
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/ErrorData.java
@@ -11,7 +11,7 @@ public final class ErrorData {
public final String message;
@Json(name = "source_type")
- public final String sourceType = "crashtracking";
+ public final String sourceType = "Crashtracking";
public final StackTrace stack;
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/ProcInfo.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/ProcInfo.java
index 36dd8f5b3b3..22b5b822f17 100644
--- a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/ProcInfo.java
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/ProcInfo.java
@@ -1,11 +1,9 @@
package datadog.crashtracking.dto;
-import java.util.Objects;
-
public final class ProcInfo {
- public final String pid;
+ public final int pid;
- public ProcInfo(String pid) {
+ public ProcInfo(int pid) {
this.pid = pid;
}
@@ -18,11 +16,11 @@ public boolean equals(Object o) {
return false;
}
ProcInfo procInfo = (ProcInfo) o;
- return Objects.equals(pid, procInfo.pid);
+ return pid == procInfo.pid;
}
@Override
public int hashCode() {
- return Objects.hashCode(pid);
+ return pid;
}
}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/SigInfo.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/SigInfo.java
index c4cb00d6b65..f95167e6ef5 100644
--- a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/SigInfo.java
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/SigInfo.java
@@ -1,29 +1,45 @@
package datadog.crashtracking.dto;
+import com.squareup.moshi.Json;
import java.util.Objects;
public class SigInfo {
- public final int number;
+ @Json(name = "si_signo")
+ public final Integer number;
+
+ @Json(name = "si_code")
+ public final Integer code;
+
+ @Json(name = "si_signo_human_readable")
public final String name;
+
+ @Json(name = "si_code_human_readable")
+ public final String action;
+
+ @Json(name = "si_addr")
public final String address;
- public SigInfo(int number, String name, String address) {
+ public SigInfo(Integer number, String name, Integer code, String action, String address) {
this.number = number;
this.name = name;
this.address = address;
+ this.code = code;
+ this.action = action;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SigInfo)) return false;
SigInfo sigInfo = (SigInfo) o;
- return number == sigInfo.number
+ return Objects.equals(number, sigInfo.number)
&& Objects.equals(name, sigInfo.name)
- && Objects.equals(address, sigInfo.address);
+ && Objects.equals(address, sigInfo.address)
+ && Objects.equals(code, sigInfo.code)
+ && Objects.equals(action, sigInfo.action);
}
@Override
public int hashCode() {
- return Objects.hash(number, name, address);
+ return Objects.hash(number, name, address, code, action);
}
}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/StackFrame.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/StackFrame.java
index e4232936935..fbe6be3abfd 100644
--- a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/StackFrame.java
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/StackFrame.java
@@ -1,16 +1,42 @@
package datadog.crashtracking.dto;
+import com.squareup.moshi.Json;
+import datadog.crashtracking.buildid.BuildInfo;
import java.util.Objects;
public final class StackFrame {
- public final String file;
+
+ public final String path;
public final Integer line;
public final String function;
- public StackFrame(String file, Integer line, String function) {
- this.file = file;
+ @Json(name = "build_id")
+ public final String buildId;
+
+ @Json(name = "build_id_type")
+ public final BuildInfo.BuildIdType buildIdType;
+
+ @Json(name = "file_type")
+ public final BuildInfo.FileType fileType;
+
+ @Json(name = "relative_address")
+ public String relativeAddress;
+
+ public StackFrame(
+ String path,
+ Integer line,
+ String function,
+ String buildId,
+ BuildInfo.BuildIdType buildIdType,
+ BuildInfo.FileType fileType,
+ String relativeAddress) {
+ this.path = path;
this.line = line;
this.function = function;
+ this.buildId = buildId;
+ this.buildIdType = buildIdType;
+ this.fileType = fileType;
+ this.relativeAddress = relativeAddress;
}
@Override
@@ -22,13 +48,17 @@ public boolean equals(Object o) {
return false;
}
StackFrame that = (StackFrame) o;
- return Objects.equals(file, that.file)
+ return Objects.equals(path, that.path)
&& Objects.equals(line, that.line)
- && Objects.equals(function, that.function);
+ && Objects.equals(function, that.function)
+ && Objects.equals(buildId, that.buildId)
+ && buildIdType == that.buildIdType
+ && fileType == that.fileType
+ && Objects.equals(relativeAddress, that.relativeAddress);
}
@Override
public int hashCode() {
- return Objects.hash(file, line, function);
+ return Objects.hash(path, line, function, buildId, buildIdType, fileType, relativeAddress);
}
}
diff --git a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/parsers/HotspotCrashLogParser.java b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/parsers/HotspotCrashLogParser.java
index fd142f9f01e..4a25ced3fcd 100644
--- a/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/parsers/HotspotCrashLogParser.java
+++ b/dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/parsers/HotspotCrashLogParser.java
@@ -3,6 +3,8 @@
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
import datadog.common.version.VersionInfo;
+import datadog.crashtracking.buildid.BuildIdCollector;
+import datadog.crashtracking.buildid.BuildInfo;
import datadog.crashtracking.dto.CrashLog;
import datadog.crashtracking.dto.ErrorData;
import datadog.crashtracking.dto.Metadata;
@@ -11,6 +13,9 @@
import datadog.crashtracking.dto.SigInfo;
import datadog.crashtracking.dto.StackFrame;
import datadog.crashtracking.dto.StackTrace;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@@ -31,6 +36,8 @@ public final class HotspotCrashLogParser {
// all lowercased
private static final String[] KNOWN_LIBRARY_NAMES = {"libjavaprofiler", "libddwaf", "libsqreen"};
+ private final BuildIdCollector buildIdCollector;
+
enum State {
NEW,
HEADER,
@@ -38,39 +45,66 @@ enum State {
SUMMARY,
THREAD,
STACKTRACE,
+ SEEK_DYNAMIC_LIBRARIES,
+ DYNAMIC_LIBRARIES,
DONE
}
private State state = State.NEW;
+ public HotspotCrashLogParser() {
+ this.buildIdCollector = new BuildIdCollector();
+ }
+
private static final Pattern PLUS_SPLITTER = Pattern.compile("\\+");
private static final Pattern SPACE_SPLITTER = Pattern.compile("\\s+");
private static final Pattern NEWLINE_SPLITTER = Pattern.compile("\n");
private static final Pattern SIGNAL_PARSER = Pattern.compile("\\s*(\\w+) \\((\\w+)\\).*");
+ private static final Pattern SIGINFO_PARSER =
+ Pattern.compile(
+ "siginfo:\\s+si_signo:\\s+(\\d+)\\s+\\((\\w+)\\),\\s+si_code:\\s+(\\d+)\\s+\\(([^)]+)\\),\\s+si_addr:\\s+(0x[0-9a-fA-F]+)");
+ private static final Pattern DYNAMIC_LIBS_PATH_PARSER =
+ Pattern.compile("^(?:0x)?[0-9a-fA-F]+(?:-[0-9a-fA-F]+)?\\s+(?:[^\\s/\\[]+\\s+)*(.*)$");
private StackFrame parseLine(String line) {
+ if (line == null || line.isEmpty()) {
+ return null;
+ }
+
String functionName = null;
Integer functionLine = null;
String filename = null;
+ String relAddress = null;
char firstChar = line.charAt(0);
+ if (line.length() > 1 && !Character.isSpaceChar(line.charAt(1))) {
+ // We can find entries like this in between the frames
+ // Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
+ return null;
+ }
switch (firstChar) {
case 'J':
{
// J 36572 c2 datadog.trace.util.AgentTaskScheduler$PeriodicTask.run()V (25 bytes) @
// 0x00007f2fd0198488 [0x00007f2fd0198420+0x0000000000000068]
+ // J 3896 c2 java.nio.ByteBuffer.allocate(I)Ljava/nio/ByteBuffer; java.base@21.0.1 (20
+ // bytes) @ 0x0000000112ad51e8 [0x0000000112ad4fc0+0x0000000000000228]
String[] parts = SPACE_SPLITTER.split(line);
- functionName = parts[3];
+ if (parts.length > 3) {
+ functionName = parts[3];
+ }
break;
}
case 'j':
{
// j one.profiler.AsyncProfiler.stop()V+1
String[] parts = PLUS_SPLITTER.split(line, 2);
- functionName = parts[0].substring(3);
- if (parts.length > 1) {
- try {
- functionLine = Integer.parseInt(parts[1]);
- } catch (Throwable ignored) {
+ if (parts.length > 0 && parts[0].length() > 3) {
+ functionName = parts[0].substring(3);
+ if (parts.length > 1) {
+ try {
+ functionLine = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException ignored) {
+ }
}
}
break;
@@ -79,23 +113,26 @@ private StackFrame parseLine(String line) {
case 'V':
{
// V [libjvm.so+0x8fc20a] thread_entry(JavaThread*, JavaThread*)+0x8a
- if (line.endsWith("]")) {
- // C [libpthread.so.0+0x13d60]
- functionName = line.substring(4, line.length() - 1);
- } else {
- int plusIdx = line.lastIndexOf('+');
- functionName =
- plusIdx > -1
- ? line.substring(line.indexOf(']') + 3, plusIdx)
- : line.substring(line.indexOf(']') + 3);
- }
+ // C [libpthread.so.0+0x13d60]
int libstart = line.indexOf('[');
if (libstart > 0) {
int libend = line.indexOf(']', libstart + 1);
if (libend > 0) {
- String[] parts = PLUS_SPLITTER.split(line.substring(libstart + 1, libend), 2);
- filename = normalizeFilename(parts[0]);
- // TODO: extract relative address for second part and send to the intake
+ String libAndRelAddress = line.substring(libstart + 1, libend);
+ String[] parts = PLUS_SPLITTER.split(libAndRelAddress, 2);
+ filename = parts[0];
+ if (parts.length > 1) {
+ relAddress = parts[1];
+ }
+
+ // Extract function name if present (after the bracket)
+ if (libend + 3 < line.length() && !line.endsWith("]")) {
+ int plusIdx = line.lastIndexOf('+');
+ functionName =
+ plusIdx > libend
+ ? line.substring(libend + 3, plusIdx).trim()
+ : line.substring(libend + 3).trim();
+ }
}
}
break;
@@ -103,23 +140,57 @@ private StackFrame parseLine(String line) {
case 'v':
{
// v ~StubRoutines::call_stub
- int plusIdx = line.lastIndexOf('+');
- functionName =
- plusIdx > -1
- ? line.substring(line.indexOf(']') + 3, plusIdx)
- : line.substring(line.indexOf(']') + 3);
+ // v ~RuntimeStub::_new_array_Java 0x00000001124cb638
+ if (line.length() > 3) {
+ String remaining = line.substring(3).trim();
+ // Check for address at the end (0x...)
+ int lastSpace = remaining.lastIndexOf(' ');
+ if (lastSpace > 0 && lastSpace + 1 < remaining.length()) {
+ String possibleAddress = remaining.substring(lastSpace + 1);
+ if (possibleAddress.startsWith("0x")) {
+ relAddress = possibleAddress;
+ remaining = remaining.substring(0, lastSpace).trim();
+ }
+ }
+ // Check for + offset
+ int plusIdx = remaining.lastIndexOf('+');
+ functionName = plusIdx > -1 ? remaining.substring(0, plusIdx).trim() : remaining;
+ }
break;
}
default:
// do nothing
break;
}
- if (functionName != null) {
- return new StackFrame(filename, functionLine, functionName);
+ if (filename != null && !filename.isEmpty()) {
+ buildIdCollector.addUnprocessedLibrary(filename);
+ }
+
+ if (functionName != null || filename != null) {
+ return new StackFrame(
+ filename,
+ functionLine,
+ stripCompilerAnnotations(functionName),
+ null,
+ null,
+ null,
+ relAddress);
}
return null;
}
+ private static String stripCompilerAnnotations(String functionName) {
+ if (functionName == null) {
+ return null;
+ }
+ // Strip compiler annotations like [clone .isra.531], [clone .constprop.0], etc.
+ int bracketIdx = functionName.lastIndexOf(" [");
+ if (bracketIdx > 0 && functionName.endsWith("]")) {
+ return functionName.substring(0, bracketIdx);
+ }
+ return functionName;
+ }
+
private static String knownLibraryPrefix(String filename) {
final String lowerCased = filename.toLowerCase(Locale.ROOT);
for (String prefix : KNOWN_LIBRARY_NAMES) {
@@ -130,7 +201,7 @@ private static String knownLibraryPrefix(String filename) {
return null;
}
- private String normalizeFilename(String filename) {
+ private static String normalizeFilename(String filename) {
if (filename == null) {
return null;
}
@@ -177,28 +248,6 @@ public CrashLog parse(String uuid, String crashLog) {
if (oomIdx > 0) {
oomMessage = line.substring(oomIdx + OOM_MARKER.length());
} else {
- String name = null, address = null;
- int number = 0;
- // first non-empty line after the message is the signal
- final Matcher signalMatcher = SIGNAL_PARSER.matcher(line.substring(3));
- // # SIGSEGV (0xb) at pc=0x00007f8b1c0b3d7d, pid=1, tid=1
- if (signalMatcher.matches()) {
- name = signalMatcher.group(1);
- try {
- number = Integer.decode(signalMatcher.group(2));
- } catch (Throwable ignored) {
- }
- }
-
- int pcIdx = line.indexOf("pc=");
- if (pcIdx > -1) {
- int endIdx = line.indexOf(',', pcIdx);
- address = line.substring(pcIdx + 3, endIdx);
- }
- if (name != null) {
- sigInfo = new SigInfo(number, name, address);
- }
-
int pidIdx = line.indexOf("pid=");
if (pidIdx > -1) {
int endIdx = line.indexOf(',', pidIdx);
@@ -229,8 +278,20 @@ public CrashLog parse(String uuid, String crashLog) {
}
break;
case STACKTRACE:
- if (line.isEmpty()) {
- state = State.DONE;
+ if (line.startsWith("siginfo:")) {
+ // siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr:
+ // 0x0000000000000070
+ final Matcher siginfoMatcher = SIGINFO_PARSER.matcher(line);
+ if (siginfoMatcher.matches()) {
+ Integer number = safelyParseInt(siginfoMatcher.group(1));
+ String name = siginfoMatcher.group(2);
+ Integer siCode = safelyParseInt(siginfoMatcher.group(3));
+ String sigAction = siginfoMatcher.group(4);
+ String address = siginfoMatcher.group(5);
+ sigInfo = new SigInfo(number, name, siCode, sigAction, address);
+ }
+ } else if (line.contains("P R O C E S S")) {
+ state = State.SEEK_DYNAMIC_LIBRARIES;
} else {
// Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
final StackFrame frame = parseLine(line);
@@ -239,8 +300,32 @@ public CrashLog parse(String uuid, String crashLog) {
}
}
break;
+ case SEEK_DYNAMIC_LIBRARIES:
+ if (line.startsWith("Dynamic libraries:")) {
+ state = State.DYNAMIC_LIBRARIES;
+ } else if (line.equals("END.")) {
+ state = State.DONE;
+ }
+ break;
+ case DYNAMIC_LIBRARIES:
+ if (line.isEmpty()) {
+ state = State.DONE;
+ }
+ final Matcher matcher = DYNAMIC_LIBS_PATH_PARSER.matcher(line);
+ if (matcher.matches()) {
+ final String pathString = matcher.group(1);
+ if (pathString != null && !pathString.isEmpty()) {
+ try {
+ final Path path = Paths.get(pathString);
+ buildIdCollector.resolveBuildId(path);
+ } catch (InvalidPathException ignored) {
+ }
+ }
+ }
+ break;
case DONE:
// skip
+ buildIdCollector.awaitCollectionDone(5);
break outer;
default:
// unexpected parser state; bail out
@@ -262,12 +347,45 @@ public CrashLog parse(String uuid, String crashLog) {
message = "Process terminated by signal " + kind;
}
+ final List enrichedFrames = new ArrayList<>(frames.size());
+
+ for (StackFrame frame : frames) {
+ // enrich with the build id if collected (best effort)
+ if (frame.path == null) {
+ enrichedFrames.add(frame);
+ continue;
+ }
+ final BuildInfo buildInfo = buildIdCollector.getBuildInfo(frame.path);
+ if (buildInfo != null) {
+ enrichedFrames.add(
+ new StackFrame(
+ normalizeFilename(frame.path),
+ frame.line,
+ frame.function,
+ buildInfo.buildId,
+ buildInfo.buildIdType,
+ buildInfo.fileType,
+ frame.relativeAddress));
+ } else {
+ enrichedFrames.add(
+ new StackFrame(
+ normalizeFilename(frame.path),
+ frame.line,
+ frame.function,
+ null,
+ null,
+ null,
+ frame.relativeAddress));
+ }
+ }
+
ErrorData error =
- new ErrorData(kind, message, new StackTrace(frames.toArray(new StackFrame[0])));
+ new ErrorData(kind, message, new StackTrace(enrichedFrames.toArray(new StackFrame[0])));
// We can not really extract the full metadata and os info from the crash log
// This code assumes the parser is run on the same machine as the crash happened
Metadata metadata = new Metadata("dd-trace-java", VersionInfo.VERSION, "java", null);
- ProcInfo procInfo = pid != null ? new ProcInfo(pid) : null;
+ Integer parsedPid = safelyParseInt(pid);
+ ProcInfo procInfo = parsedPid != null ? new ProcInfo(parsedPid) : null;
return new CrashLog(
uuid, incomplete, datetime, error, metadata, OSInfo.current(), procInfo, sigInfo, "1.0");
}
@@ -285,4 +403,15 @@ static String dateTimeToISO(String datetime) {
}
}
}
+
+ static Integer safelyParseInt(String value) {
+ if (value == null) {
+ return null;
+ }
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
}
diff --git a/dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/buildid/BuildIdCollectorTest.java b/dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/buildid/BuildIdCollectorTest.java
new file mode 100644
index 00000000000..a145d09997b
--- /dev/null
+++ b/dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/buildid/BuildIdCollectorTest.java
@@ -0,0 +1,88 @@
+package datadog.crashtracking.buildid;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+public class BuildIdCollectorTest {
+
+ @TempDir Path tempDir;
+
+ @Test
+ void testAwaitCollectionDoneWithinTimeout() throws IOException {
+ BuildIdCollector collector = new BuildIdCollector();
+
+ // Add a library to process
+ String filename = "test-library.so";
+ collector.addUnprocessedLibrary(filename);
+
+ // Create a simple test file
+ Path testFile = tempDir.resolve(filename);
+ Files.write(testFile, new byte[] {0x7F, 'E', 'L', 'F'}); // ELF magic bytes
+
+ // Start collection
+ collector.resolveBuildId(testFile);
+
+ // Should complete within timeout
+ long startTime = System.currentTimeMillis();
+ collector.awaitCollectionDone(5);
+ long elapsedTime = System.currentTimeMillis() - startTime;
+
+ // Verify it completed quickly (well under 5 seconds)
+ assertTrue(elapsedTime < 5000, "Collection should complete quickly");
+ }
+
+ @Test
+ void testAwaitCollectionDoneWithoutStartingCollection() {
+ BuildIdCollector collector = new BuildIdCollector();
+
+ // awaitCollectionDone without starting collection should return immediately
+ long startTime = System.currentTimeMillis();
+ collector.awaitCollectionDone(5);
+ long elapsedTime = System.currentTimeMillis() - startTime;
+
+ assertTrue(elapsedTime < 100, "Should return immediately when not collecting");
+ }
+
+ @Test
+ void testResolveBuildIdSkipsUnprocessedLibraries() throws IOException {
+ BuildIdCollector collector = new BuildIdCollector();
+
+ String filename = "not-added.so";
+ Path testFile = tempDir.resolve(filename);
+ Files.write(testFile, new byte[] {0x7F, 'E', 'L', 'F'});
+
+ // Resolve without adding first
+ collector.resolveBuildId(testFile);
+ collector.awaitCollectionDone(1);
+
+ // Should not be in the map since it wasn't added
+ BuildInfo info = collector.getBuildInfo(filename);
+ assertNull(info, "Library should not be processed if not added first");
+ }
+
+ @Test
+ void testMultipleAwaitCollectionDoneCalls() throws IOException {
+ BuildIdCollector collector = new BuildIdCollector();
+
+ String filename = "test.so";
+ collector.addUnprocessedLibrary(filename);
+ Path testFile = tempDir.resolve(filename);
+ Files.write(testFile, new byte[] {0x7F, 'E', 'L', 'F'});
+
+ collector.resolveBuildId(testFile);
+ collector.awaitCollectionDone(5);
+
+ // Second call should return immediately since collection is already done
+ long startTime = System.currentTimeMillis();
+ collector.awaitCollectionDone(5);
+ long elapsedTime = System.currentTimeMillis() - startTime;
+
+ assertTrue(elapsedTime < 100, "Second await should return immediately");
+ }
+}
diff --git a/dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/buildid/BuildIdExtractorIntegrationTest.java b/dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/buildid/BuildIdExtractorIntegrationTest.java
new file mode 100644
index 00000000000..6e2deffc8c6
--- /dev/null
+++ b/dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/buildid/BuildIdExtractorIntegrationTest.java
@@ -0,0 +1,172 @@
+package datadog.crashtracking.buildid;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * Integration tests for BuildIdExtractor implementations using Docker containers. Tests validate
+ * build ID extraction from real ELF (Unix/Linux) and PE (Windows) binaries.
+ */
+public class BuildIdExtractorIntegrationTest {
+ private static GenericContainer> linuxContainer;
+ private static GenericContainer> dotnetContainer;
+ @TempDir private static Path tempDir;
+ private static final Logger logger =
+ LoggerFactory.getLogger(BuildIdExtractorIntegrationTest.class);
+
+ @BeforeAll
+ static void startContainers() throws IOException {
+ // Start Ubuntu container for ELF testing
+ // Use linux/amd64 platform to ensure x86_64 binaries are available
+ linuxContainer =
+ new GenericContainer<>(
+ DockerImageName.parse("ubuntu:22.04").asCompatibleSubstituteFor("ubuntu"))
+ .withCommand("sleep", "infinity")
+ .withStartupTimeout(Duration.ofMinutes(2))
+ .withCreateContainerCmdModifier(cmd -> cmd.withPlatform("linux/amd64"));
+ linuxContainer.start();
+
+ // Start dotnet SDK container for PE testing
+ // Use linux/amd64 platform to ensure consistent binary format
+ dotnetContainer =
+ new GenericContainer<>(
+ DockerImageName.parse("mcr.microsoft.com/dotnet/sdk:8.0")
+ .asCompatibleSubstituteFor("dotnet"))
+ .withCommand("sleep", "infinity")
+ .withStartupTimeout(Duration.ofMinutes(2))
+ .withCreateContainerCmdModifier(cmd -> cmd.withPlatform("linux/amd64"));
+ dotnetContainer.start();
+ }
+
+ @AfterAll
+ static void stopContainers() throws IOException {
+ // Stop containers
+ if (linuxContainer != null) {
+ linuxContainer.stop();
+ }
+ if (dotnetContainer != null) {
+ dotnetContainer.stop();
+ }
+ }
+
+ /**
+ * Copy a binary file from a container to the local temp directory.
+ *
+ * @param container The container to copy from
+ * @param containerPath Path to the binary inside the container
+ * @return Path to the copied binary in the local temp directory
+ */
+ private Path copyFromContainer(GenericContainer> container, String containerPath)
+ throws IOException {
+ String filename = Paths.get(containerPath).getFileName().toString();
+ Path localPath = tempDir.resolve(filename + "-" + System.nanoTime());
+
+ container.copyFileFromContainer(containerPath, localPath.toString());
+
+ if (!Files.exists(localPath) || Files.size(localPath) == 0) {
+ throw new IOException("Failed to copy binary: " + containerPath);
+ }
+
+ return localPath;
+ }
+
+ private static Stream elfBinaries() {
+ return Stream.of(
+ Arguments.of("/lib/x86_64-linux-gnu/libc.so.6", "GNU C Library"),
+ Arguments.of("/lib/x86_64-linux-gnu/libm.so.6", "Math library"),
+ Arguments.of("/lib/x86_64-linux-gnu/libpthread.so.0", "POSIX threads library"));
+ }
+
+ @ParameterizedTest(name = "ELF: {1}")
+ @MethodSource("elfBinaries")
+ void testElfBuildIdExtraction(String containerPath, String description) throws Exception {
+ Path localBinary = copyFromContainer(linuxContainer, containerPath);
+
+ ElfBuildIdExtractor extractor = new ElfBuildIdExtractor();
+ String buildId = extractor.extractBuildId(localBinary);
+
+ logger.info("Found build ID: {} for library {}", buildId, localBinary);
+
+ assertNotNull(buildId, "Build ID should be found for " + description);
+ assertTrue(
+ buildId.matches("^[0-9a-f]{32,40}$"), "Build ID should be 32-40 hex chars: " + buildId);
+ assertEquals(BuildInfo.FileType.ELF, extractor.fileType());
+ assertEquals(BuildInfo.BuildIdType.GNU, extractor.buildIdType());
+ }
+
+ private static Stream peBinaries() {
+ return Stream.of(
+ Arguments.of(
+ "/usr/share/dotnet/shared/Microsoft.NETCore.App/8.0.23/System.Private.CoreLib.dll",
+ "Core .NET Library"),
+ Arguments.of(
+ "/usr/share/dotnet/shared/Microsoft.NETCore.App/8.0.23/System.Runtime.dll",
+ ".NET Runtime"),
+ Arguments.of(
+ "/usr/share/dotnet/shared/Microsoft.NETCore.App/8.0.23/System.Console.dll",
+ "Console Library"),
+ Arguments.of(
+ "/usr/share/dotnet/shared/Microsoft.NETCore.App/8.0.23/Microsoft.CSharp.dll",
+ "C# Compiler Library"));
+ }
+
+ @ParameterizedTest(name = "PE: {1}")
+ @MethodSource("peBinaries")
+ void testPeBuildIdExtraction(String containerPath, String description) throws Exception {
+ Path localBinary = copyFromContainer(dotnetContainer, containerPath);
+
+ PeBuildIdExtractor extractor = new PeBuildIdExtractor();
+ String buildId = extractor.extractBuildId(localBinary);
+
+ logger.info("Found build ID: {} for library {}", buildId, localBinary);
+
+ assertNotNull(buildId, "GUID+Age should be found for " + description);
+
+ // Build ID format: GUID (32 uppercase hex chars) + Age (lowercase hex, variable length)
+ assertTrue(
+ buildId.length() >= 33,
+ "Build ID should be at least 33 hex chars (GUID + Age): " + buildId);
+
+ // Verify format: GUID part (32 chars) should be uppercase, Age part should be lowercase
+ String guidPart = buildId.substring(0, 32);
+ String agePart = buildId.substring(32);
+
+ assertTrue(
+ guidPart.matches("^[0-9A-F]{32}$"),
+ "GUID part (first 32 chars) should be uppercase hex: " + guidPart);
+ assertTrue(
+ agePart.matches("^[0-9a-f]+$"),
+ "Age part (remaining chars) should be lowercase hex: " + agePart);
+
+ // Verify both parts parse correctly as hex
+ try {
+ new java.math.BigInteger(guidPart, 16);
+ Long.parseLong(agePart, 16);
+ } catch (NumberFormatException e) {
+ throw new AssertionError("Build ID should be valid hex: " + buildId, e);
+ }
+
+ logger.info("Build ID format verified: GUID={}, Age={}", guidPart, agePart);
+
+ assertEquals(BuildInfo.FileType.PE, extractor.fileType());
+ assertEquals(BuildInfo.BuildIdType.PDB, extractor.buildIdType());
+ }
+}
diff --git a/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-2.txt b/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-2.txt
index 6020ddd1ee0..27d3e1b665e 100644
--- a/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-2.txt
+++ b/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-2.txt
@@ -1 +1 @@
-{"timestamp":"2024-09-20T13:19:06Z","ddsource":"crashtracker","error":{"source_type":"crashtracking","is_crash":true,"type":"SIGSEGV","message":"Process terminated by signal SIGSEGV","stack":{"format":"CrashTrackerV1","frames":[{"file":"libpthread.so.0","function":"__pthread_clockjoin_ex"}]}},"sig_info":{"si_addr":"0x00007f011ab1ccd5 (sent by kill)","si_signo_human_readable":"SIGSEGV","si_signo":11}}
+ {"timestamp":"2024-09-20T13:19:06Z","ddsource":"crashtracker","error":{"is_crash":true,"type":"UNKNOWN","message":"Process terminated by signal UNKNOWN","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"function":"__pthread_clockjoin_ex","path":"libpthread.so.0","relative_address":"0x9cd5"}]}}}
diff --git a/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-3.txt b/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-3.txt
index e1000645dcc..791ff9408d2 100644
--- a/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-3.txt
+++ b/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-3.txt
@@ -1 +1 @@
-{"ddsource":"crashtracker","error":{"is_crash":true,"type":"OutOfMemory","message":"Java heap space","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libjvm.dylib","function":"VMError::report_and_die()"},{"file":"libjvm.dylib","function":"report_vm_error(char const*, int, char const*, char const*)"},{"file":"libjvm.dylib","function":"report_java_out_of_memory(char const*)"},{"file":"libjvm.dylib","function":"CollectedHeap::common_mem_allocate_noinit(KlassHandle, unsigned long, Thread*)"},{"file":"libjvm.dylib","function":"TypeArrayKlass::allocate_common(int, bool, Thread*)"},{"file":"libjvm.dylib","function":"InterpreterRuntime::newarray(JavaThread*, BasicType, int)"},{"function":"datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V","line":105},{"function":" ~StubRoutines::call_stub"},{"file":"libjvm.dylib","function":"JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)"},{"file":"libjvm.dylib","function":"jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)"},{"file":"libjvm.dylib","function":"jni_CallStaticVoidMethod"},{"file":"java","function":"JavaMain"},{"file":"libsystem_pthread.dylib","function":"_pthread_start"},{"file":"libsystem_pthread.dylib","function":"thread_start"}]}},"sig_info":{"si_addr":"0x0000000000000000","si_signo_human_readable":"INVALID","si_signo":0}}
+{"ddsource":"crashtracker","error":{"is_crash":true,"type":"OutOfMemory","message":"Java heap space","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"function":"VMError::report_and_die()","path":"libjvm.dylib","relative_address":"0x565d30"},{"function":"report_vm_error(char const*, int, char const*, char const*)","path":"libjvm.dylib","relative_address":"0x1941a0"},{"function":"report_java_out_of_memory(char const*)","path":"libjvm.dylib","relative_address":"0x1943d8"},{"function":"CollectedHeap::common_mem_allocate_noinit(KlassHandle, unsigned long, Thread*)","path":"libjvm.dylib","relative_address":"0x70430"},{"function":"TypeArrayKlass::allocate_common(int, bool, Thread*)","path":"libjvm.dylib","relative_address":"0x53eba8"},{"function":"InterpreterRuntime::newarray(JavaThread*, BasicType, int)","path":"libjvm.dylib","relative_address":"0x285b6c"},{"function":"datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V","line":105},{"function":"~StubRoutines::call_stub"},{"function":"JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)","path":"libjvm.dylib","relative_address":"0x28f86c"},{"function":"jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)","path":"libjvm.dylib","relative_address":"0x2d3b44"},{"function":"jni_CallStaticVoidMethod","path":"libjvm.dylib","relative_address":"0x2d7160"},{"function":"JavaMain","path":"java","relative_address":"0x6404"},{"function":"_pthread_start","path":"libsystem_pthread.dylib","relative_address":"0x6f94"},{"function":"thread_start","path":"libsystem_pthread.dylib","relative_address":"0x1d34"},{"function":"datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V","line":105},{"function":"~StubRoutines::call_stub"}]}}}
diff --git a/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry.txt b/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry.txt
index 034c42b7b06..8503e816fa8 100644
--- a/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry.txt
+++ b/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry.txt
@@ -1 +1 @@
-{"timestamp":"2023-10-17T20:25:14+08:00","ddsource":"crashtracker","error":{"is_crash":true,"type":"SIGSEGV","message":"Process terminated by signal SIGSEGV","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libjvm.so","function":"vframeStreamForte::forte_next()"},{"file":"libjvm.so","function":"forte_fill_call_trace_given_top(JavaThread*, ASGCT_CallTrace*, int, frame) [clone .isra.22]"},{"file":"libjvm.so","function":"AsyncGetCallTrace"},{"file":"libjavaProfiler.so","function":"Profiler::getJavaTraceAsync(void*, ASGCT_CallFrame*, int, StackContext*, bool*) [clone .isra.531]"},{"file":"libjavaProfiler.so","function":"Profiler::recordSample(void*, unsigned long long, int, int, Event*)"},{"file":"libjavaProfiler.so","function":"WallClock::sharedSignalHandler(int, siginfo_t*, void*)"},{"file":"libpthread.so.0","function":"libpthread.so.0+0x12cf0"},{"file":"libjvm.so","function":"JfrStackTrace::record_safe(JavaThread*, int)"},{"file":"libjvm.so","function":"JfrStackTraceRepository::record_for_leak_profiler(JavaThread*, int)"},{"file":"libjvm.so","function":"ObjectSampler::sample(HeapWordImpl**, unsigned long, JavaThread*)"},{"file":"libjvm.so","function":"JfrAllocationTracer::JfrAllocationTracer(Klass const*, HeapWordImpl**, unsigned long, bool, JavaThread*)"},{"file":"libjvm.so","function":"AllocTracer::send_allocation_in_new_tlab(Klass*, HeapWordImpl**, unsigned long, unsigned long, JavaThread*)"},{"file":"libjvm.so","function":"MemAllocator::allocate() const"},{"file":"libjvm.so","function":"InstanceKlass::allocate_objArray(int, int, JavaThread*)"},{"file":"libjvm.so","function":"OptoRuntime::new_array_C(Klass*, int, JavaThread*)"}]}},"sig_info":{"si_addr":"0x00007f37a18bc187","si_signo_human_readable":"SIGSEGV","si_signo":11}}
+{"timestamp":"2023-10-17T20:25:14+08:00","ddsource":"crashtracker","error":{"is_crash":true,"type":"SIGSEGV","message":"Process terminated by signal SIGSEGV","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"function":"vframeStreamForte::forte_next()","path":"libjvm.so","relative_address":"0x6b7187"},{"function":"forte_fill_call_trace_given_top(JavaThread*, ASGCT_CallTrace*, int, frame)","path":"libjvm.so","relative_address":"0x6b79ad"},{"function":"AsyncGetCallTrace","path":"libjvm.so","relative_address":"0x6b8123"},{"function":"Profiler::getJavaTraceAsync(void*, ASGCT_CallFrame*, int, StackContext*, bool*)","path":"libjavaProfiler.so","relative_address":"0x146c8"},{"function":"Profiler::recordSample(void*, unsigned long long, int, int, Event*)","path":"libjavaProfiler.so","relative_address":"0x1c18d"},{"function":"WallClock::sharedSignalHandler(int, siginfo_t*, void*)","path":"libjavaProfiler.so","relative_address":"0x1d128"},{"path":"libpthread.so.0","relative_address":"0x12cf0"},{"function":"JfrStackTrace::record_safe(JavaThread*, int)","path":"libjvm.so","relative_address":"0x85f9e9"},{"function":"JfrStackTraceRepository::record_for_leak_profiler(JavaThread*, int)","path":"libjvm.so","relative_address":"0x861650"},{"function":"ObjectSampler::sample(HeapWordImpl**, unsigned long, JavaThread*)","path":"libjvm.so","relative_address":"0xb1ecba"},{"function":"JfrAllocationTracer::JfrAllocationTracer(Klass const*, HeapWordImpl**, unsigned long, bool, JavaThread*)","path":"libjvm.so","relative_address":"0x81abee"},{"function":"AllocTracer::send_allocation_in_new_tlab(Klass*, HeapWordImpl**, unsigned long, unsigned long, JavaThread*)","path":"libjvm.so","relative_address":"0x3afa20"},{"function":"MemAllocator::allocate() const","path":"libjvm.so","relative_address":"0xa8f8ed"},{"function":"InstanceKlass::allocate_objArray(int, int, JavaThread*)","path":"libjvm.so","relative_address":"0x7e443c"},{"function":"OptoRuntime::new_array_C(Klass*, int, JavaThread*)","path":"libjvm.so","relative_address":"0xbe1b59"},{"function":"~RuntimeStub::_new_array_Java"},{"function":"java.util.HashMap.resize()[Ljava/util/HashMap$Node;"},{"function":"java.util.HashMap.putVal(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/lang/Object;"},{"function":"java.util.HashSet.add(Ljava/lang/Object;)Z"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.doTransform(Lorg/w3c/dom/Node;Ljava/io/PrintStream;IZZZZZ)V"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.doTransform(Lorg/w3c/dom/Node;Ljava/io/PrintStream;IZZZZZ)V"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.doTransform(Lorg/w3c/dom/Node;Ljava/io/PrintStream;IZZZZZ)V"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.doTransform(Lorg/w3c/dom/Node;Ljava/io/PrintStream;IZZZZZ)V"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.partToString(Lorg/w3c/dom/Node;)Ljava/lang/String;"},{"function":"com.REDACT_THIS.message.impl.MessagePartDOMExt.writeExternal(Ljava/io/ObjectOutput;)V"},{"function":"java.io.ObjectOutputStream.writeOrdinaryObject(Ljava/lang/Object;Ljava/io/ObjectStreamClass;Z)V"},{"function":"java.io.ObjectOutputStream.writeObject0(Ljava/lang/Object;Z)V"},{"function":"jdk.internal.reflect.GeneratedMethodAccessor173.invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"},{"function":"java.io.ObjectOutputStream.writeSerialData(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V"},{"function":"java.io.ObjectOutputStream.writeOrdinaryObject(Ljava/lang/Object;Ljava/io/ObjectStreamClass;Z)V"},{"function":"java.io.ObjectOutputStream.writeObject0(Ljava/lang/Object;Z)V"},{"function":"com.REDACT_THIS.message.impl.Message.writeExternal(Ljava/io/ObjectOutput;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.ExternalizableConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshallField(Lcom/REDACT_THIS/xstream/converters/MarshallingContext;Ljava/lang/Object;Ljava/lang/reflect/Field;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshallField(Lcom/REDACT_THIS/xstream/converters/MarshallingContext;Ljava/lang/Object;Ljava/lang/reflect/Field;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshallField(Lcom/REDACT_THIS/xstream/converters/MarshallingContext;Ljava/lang/Object;Ljava/lang/reflect/Field;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshallField(Lcom/REDACT_THIS/xstream/converters/MarshallingContext;Ljava/lang/Object;Ljava/lang/reflect/Field;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.XStream.toXML(Ljava/lang/Object;Ljava/io/OutputStream;)V"},{"function":"com.REDACT_THIS.recover.impl.fs.FSJobSerializer.serializeJob(Ljava/lang/String;Lcom/REDACT_THIS/recover/impl/GenericRecoveryJob;)V"},{"function":"com.REDACT_THIS.recover.impl.db.RecoveryPoolDBImpl.specificSave(Lcom/REDACT_THIS/recover/spi/RecoveryJob;)V"},{"function":"com.REDACT_THIS.recover.impl.REDACT_THIS.saveJob(Lcom/REDACT_THIS/recover/spi/RecoveryJob;ILjava/lang/String;)V"},{"function":"com.REDACT_THIS.frame.core.FrameWorkListener.callbackNewData(Lcom/REDACT_THIS/frame/header/GenericTask;Ljava/util/concurrent/ExecutorService;)V"},{"function":"com.REDACT_THIS.frame.connection.direct.DirectModeMessageListener.onMessage(Ljavax/jms/Message;)V"},{"function":"datadog.trace.instrumentation.jms.DatadogMessageListener.onMessage(Ljavax/jms/Message;)V"},{"function":"org.apache.activemq.artemis.jms.client.JMSMessageListenerWrapper.onMessage(Lorg/apache/activemq/artemis/api/core/client/ClientMessage;)V"},{"function":"org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl$Runner.run()V"},{"function":"org.apache.activemq.artemis.utils.actors.ProcessorBase$$Lambda$2929+0x00000007ce44f8f8.run()V"},{"function":"java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V"},{"function":"java.util.concurrent.ThreadPoolExecutor$Worker.run()V"},{"function":"org.apache.activemq.artemis.utils.ActiveMQThreadFactory$1.run()V","line":44},{"function":"~StubRoutines::call_stub"}]}},"sig_info":{"si_addr":"0x0000000000000070","si_signo_human_readable":"SIGSEGV","si_signo":11}}
diff --git a/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample_oom.txt b/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample_oom.txt
index aec5691b6af..6771b6c2f23 100644
--- a/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample_oom.txt
+++ b/dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample_oom.txt
@@ -1 +1 @@
-{"timestamp":"2025-11-24T09:43:29+01:00","ddsource":"crashtracker","error":{"is_crash":true,"type":"OutOfMemory","message":"Java heap space","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libjvm.dylib","function":"VMError::report_and_die(int, char const*, char const*, char*, Thread*, unsigned char*, void*, void*, char const*, int, unsigned long)"},{"file":"libjvm.dylib","function":"report_fatal(VMErrorType, char const*, int, char const*, ...)"},{"file":"libjvm.dylib","function":"report_java_out_of_memory(char const*)"},{"file":"libjvm.dylib","function":"MemAllocator::Allocation::check_out_of_memory()"},{"file":"libjvm.dylib","function":"MemAllocator::allocate() const"},{"file":"libjvm.dylib","function":"CollectedHeap::array_allocate(Klass*, unsigned long, int, bool, JavaThread*)"},{"file":"libjvm.dylib","function":"OptoRuntime::new_array_C(Klass*, int, JavaThread*)"},{"function":"Java"},{"function":" ~RuntimeStub::_new_array_Java 0x00000001124cb638"},{"function":"java.nio.ByteBuffer.allocate(I)Ljava/nio/ByteBuffer;"},{"function":"datadog.communication.serialization.FlushingBuffer.(ILdatadog/communication/serialization/ByteBufferConsumer;)V","line":6},{"function":"datadog.trace.agent.common.writer.PayloadDispatcherImpl.selectMapper()V","line":126},{"function":"datadog.trace.agent.common.writer.PayloadDispatcherImpl.addTrace(Ljava/util/List;)V","line":1},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.onEvent(Ljava/lang/Object;)V","line":22},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.consumeFromPrimaryQueue()V","line":21},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.runDutyCycle()V","line":12},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.run()V","line":1},{"function":"java.lang.Thread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V"},{"function":"java.lang.Thread.run()V"},{"function":" ~StubRoutines::call_stub 0x00000001123b0140"}]}}}
+{"timestamp":"2025-11-24T09:43:29+01:00","ddsource":"crashtracker","error":{"is_crash":true,"type":"OutOfMemory","message":"Java heap space","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"function":"VMError::report_and_die(int, char const*, char const*, char*, Thread*, unsigned char*, void*, void*, char const*, int, unsigned long)","path":"libjvm.dylib","relative_address":"0xaa5f24"},{"function":"report_fatal(VMErrorType, char const*, int, char const*, ...)","path":"libjvm.dylib","relative_address":"0x314c74"},{"function":"report_java_out_of_memory(char const*)","path":"libjvm.dylib","relative_address":"0x315248"},{"function":"MemAllocator::Allocation::check_out_of_memory()","path":"libjvm.dylib","relative_address":"0x7d9764"},{"function":"MemAllocator::allocate() const","path":"libjvm.dylib","relative_address":"0x7da2f4"},{"function":"CollectedHeap::array_allocate(Klass*, unsigned long, int, bool, JavaThread*)","path":"libjvm.dylib","relative_address":"0x2c3de4"},{"function":"OptoRuntime::new_array_C(Klass*, int, JavaThread*)","path":"libjvm.dylib","relative_address":"0x8ea9ec"},{"function":"~RuntimeStub::_new_array_Java","relative_address":"0x00000001124cb638"},{"function":"java.nio.ByteBuffer.allocate(I)Ljava/nio/ByteBuffer;"},{"function":"datadog.communication.serialization.FlushingBuffer.(ILdatadog/communication/serialization/ByteBufferConsumer;)V","line":6},{"function":"datadog.trace.agent.common.writer.PayloadDispatcherImpl.selectMapper()V","line":126},{"function":"datadog.trace.agent.common.writer.PayloadDispatcherImpl.addTrace(Ljava/util/List;)V","line":1},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.onEvent(Ljava/lang/Object;)V","line":22},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.consumeFromPrimaryQueue()V","line":21},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.runDutyCycle()V","line":12},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.run()V","line":1},{"function":"java.lang.Thread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V"},{"function":"java.lang.Thread.run()V"},{"function":"~StubRoutines::call_stub","relative_address":"0x00000001123b0140"}]}}} ==> expected: <{data_schema_version=1.0, error={is_crash=true, kind=OutOfMemory, message=Java heap space, source_type=Crashtracking, stack={format=CrashTrackerV1, frames=[{function=VMError::report_and_die(int, char const*, char const*, char*, Thread*, unsigned char*, void*, void*, char const*, int, unsigned long), path=libjvm.dylib, relative_address=0xaa5f24}, {function=report_fatal(VMErrorType, char const*, int, char const*, ...), path=libjvm.dylib, relative_address=0x314c74}, {function=report_java_out_of_memory(char const*), path=libjvm.dylib, relative_address=0x315248}, {function=MemAllocator::Allocation::check_out_of_memory(), path=libjvm.dylib, relative_address=0x7d9764}, {function=MemAllocator::allocate() const, path=libjvm.dylib, relative_address=0x7da2f4}, {function=CollectedHeap::array_allocate(Klass*, unsigned long, int, bool, JavaThread*), path=libjvm.dylib, relative_address=0x2c3de4}, {function=OptoRuntime::new_array_C(Klass*, int, JavaThread*), path=libjvm.dylib, relative_address=0x8ea9ec}, {function=~RuntimeStub::_new_array_Java, relative_address=0x00000001124cb638}, {function=java.nio.ByteBuffer.allocate(I)Ljava/nio/ByteBuffer;}, {function=datadog.communication.serialization.FlushingBuffer.(ILdatadog/communication/serialization/ByteBufferConsumer;)V, line=6}, {function=datadog.trace.agent.common.writer.PayloadDispatcherImpl.selectMapper()V, line=126}, {function=datadog.trace.agent.common.writer.PayloadDispatcherImpl.addTrace(Ljava/util/List;)V, line=1}, {function=datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.onEvent(Ljava/lang/Object;)V, line=22}, {function=datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.consumeFromPrimaryQueue()V, line=21}, {function=datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.runDutyCycle()V, line=12}, {function=datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.run()V, line=1}, {function=java.lang.Thread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V}, {function=java.lang.Thread.run()V}, {function=~StubRoutines::call_stub, relative_address=0x00000001123b0140}]}}, incomplete=false, metadata={family=java, library_name=dd-trace-java, library_version=1.59.0-SNAPSHOT~896c1ad986}, proc_info={pid=74045}, timestamp=2025-11-24T09:43:29+01:00, uuid=a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3, version_id=0}> but was: <{timestamp=2025-11-24T09:43:29+01:00, ddsource=crashtracker, error={is_crash=true, type=OutOfMemory, message=Java heap space, source_type=crashtracking, stack={format=CrashTrackerV1, frames=[{function=VMError::report_and_die(int, char const*, char const*, char*, Thread*, unsigned char*, void*, void*, char const*, int, unsigned long), path=libjvm.dylib, relative_address=0xaa5f24}, {function=report_fatal(VMErrorType, char const*, int, char const*, ...), path=libjvm.dylib, relative_address=0x314c74}, {function=report_java_out_of_memory(char const*), path=libjvm.dylib, relative_address=0x315248}, {function=MemAllocator::Allocation::check_out_of_memory(), path=libjvm.dylib, relative_address=0x7d9764}, {function=MemAllocator::allocate() const, path=libjvm.dylib, relative_address=0x7da2f4}, {function=CollectedHeap::array_allocate(Klass*, unsigned long, int, bool, JavaThread*), path=libjvm.dylib, relative_address=0x2c3de4}, {function=OptoRuntime::new_array_C(Klass*, int, JavaThread*), path=libjvm.dylib, relative_address=0x8ea9ec}, {function=~RuntimeStub::_new_array_Java, relative_address=0x00000001124cb638}, {function=java.nio.ByteBuffer.allocate(I)Ljava/nio/ByteBuffer;}, {function=datadog.communication.serialization.FlushingBuffer.(ILdatadog/communication/serialization/ByteBufferConsumer;)V, line=6}, {function=datadog.trace.agent.common.writer.PayloadDispatcherImpl.selectMapper()V, line=126}, {function=datadog.trace.agent.common.writer.PayloadDispatcherImpl.addTrace(Ljava/util/List;)V, line=1}, {function=datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.onEvent(Ljava/lang/Object;)V, line=22}, {function=datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.consumeFromPrimaryQueue()V, line=21}, {function=datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.runDutyCycle()V, line=12}, {function=datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.run()V, line=1}, {function=java.lang.Thread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V}, {function=java.lang.Thread.run()V}, {function=~StubRoutines::call_stub, relative_address=0x00000001123b0140}]}}}
diff --git a/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry-2.txt b/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry-2.txt
index 4e8611fc812..d2340d9dd3e 100644
--- a/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry-2.txt
+++ b/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry-2.txt
@@ -1 +1 @@
-{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"SIGSEGV","message":"Process terminated by signal SIGSEGV","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libpthread.so.0","function":"__pthread_clockjoin_ex"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.57.0-SNAPSHOT~0882768757"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":"576034"},"sig_info":{"address":"0x00007f011ab1ccd5 (sent by kill)","name":"SIGSEGV","number":11},"timestamp":"2024-09-20T13:19:06Z","uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}
+{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"UNKNOWN","message":"Process terminated by signal UNKNOWN","source_type":"Crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"function":"__pthread_clockjoin_ex","path":"libpthread.so.0","relative_address":"0x9cd5"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.59.0-SNAPSHOT~896c1ad986"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":576034},"timestamp":"2024-09-20T13:19:06Z","uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}
diff --git a/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry-3.txt b/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry-3.txt
index d0303a8c863..fb6695894b0 100644
--- a/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry-3.txt
+++ b/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry-3.txt
@@ -1 +1 @@
-{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"OutOfMemory","message":"Java heap space","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libjvm.dylib","function":"VMError::report_and_die()"},{"file":"libjvm.dylib","function":"report_vm_error(char const*, int, char const*, char const*)"},{"file":"libjvm.dylib","function":"report_java_out_of_memory(char const*)"},{"file":"libjvm.dylib","function":"CollectedHeap::common_mem_allocate_noinit(KlassHandle, unsigned long, Thread*)"},{"file":"libjvm.dylib","function":"TypeArrayKlass::allocate_common(int, bool, Thread*)"},{"file":"libjvm.dylib","function":"InterpreterRuntime::newarray(JavaThread*, BasicType, int)"},{"function":"datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V","line":105},{"function":" ~StubRoutines::call_stub"},{"file":"libjvm.dylib","function":"JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)"},{"file":"libjvm.dylib","function":"jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)"},{"file":"libjvm.dylib","function":"jni_CallStaticVoidMethod"},{"file":"java","function":"JavaMain"},{"file":"libsystem_pthread.dylib","function":"_pthread_start"},{"file":"libsystem_pthread.dylib","function":"thread_start"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.57.0-SNAPSHOT~2563480753"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":"96267"},"sig_info":{"address":"0x0000000000000000","name":"INVALID","number":0},"uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}
+{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"OutOfMemory","message":"Java heap space","source_type":"Crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"function":"VMError::report_and_die()","path":"libjvm.dylib","relative_address":"0x565d30"},{"function":"report_vm_error(char const*, int, char const*, char const*)","path":"libjvm.dylib","relative_address":"0x1941a0"},{"function":"report_java_out_of_memory(char const*)","path":"libjvm.dylib","relative_address":"0x1943d8"},{"function":"CollectedHeap::common_mem_allocate_noinit(KlassHandle, unsigned long, Thread*)","path":"libjvm.dylib","relative_address":"0x70430"},{"function":"TypeArrayKlass::allocate_common(int, bool, Thread*)","path":"libjvm.dylib","relative_address":"0x53eba8"},{"function":"InterpreterRuntime::newarray(JavaThread*, BasicType, int)","path":"libjvm.dylib","relative_address":"0x285b6c"},{"function":"datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V","line":105},{"function":"~StubRoutines::call_stub"},{"function":"JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)","path":"libjvm.dylib","relative_address":"0x28f86c"},{"function":"jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)","path":"libjvm.dylib","relative_address":"0x2d3b44"},{"function":"jni_CallStaticVoidMethod","path":"libjvm.dylib","relative_address":"0x2d7160"},{"function":"JavaMain","path":"java","relative_address":"0x6404"},{"function":"_pthread_start","path":"libsystem_pthread.dylib","relative_address":"0x6f94"},{"function":"thread_start","path":"libsystem_pthread.dylib","relative_address":"0x1d34"},{"function":"datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V","line":105},{"function":"~StubRoutines::call_stub"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.59.0-SNAPSHOT~896c1ad986"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":96267},"uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}
diff --git a/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry.txt b/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry.txt
index 4532e28e72d..eae4c373e8b 100644
--- a/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry.txt
+++ b/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample-crash-for-telemetry.txt
@@ -1 +1 @@
-{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"SIGSEGV","message":"Process terminated by signal SIGSEGV","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libjvm.so","function":"vframeStreamForte::forte_next()"},{"file":"libjvm.so","function":"forte_fill_call_trace_given_top(JavaThread*, ASGCT_CallTrace*, int, frame) [clone .isra.22]"},{"file":"libjvm.so","function":"AsyncGetCallTrace"},{"file":"libjavaProfiler.so","function":"Profiler::getJavaTraceAsync(void*, ASGCT_CallFrame*, int, StackContext*, bool*) [clone .isra.531]"},{"file":"libjavaProfiler.so","function":"Profiler::recordSample(void*, unsigned long long, int, int, Event*)"},{"file":"libjavaProfiler.so","function":"WallClock::sharedSignalHandler(int, siginfo_t*, void*)"},{"file":"libpthread.so.0","function":"libpthread.so.0+0x12cf0"},{"file":"libjvm.so","function":"JfrStackTrace::record_safe(JavaThread*, int)"},{"file":"libjvm.so","function":"JfrStackTraceRepository::record_for_leak_profiler(JavaThread*, int)"},{"file":"libjvm.so","function":"ObjectSampler::sample(HeapWordImpl**, unsigned long, JavaThread*)"},{"file":"libjvm.so","function":"JfrAllocationTracer::JfrAllocationTracer(Klass const*, HeapWordImpl**, unsigned long, bool, JavaThread*)"},{"file":"libjvm.so","function":"AllocTracer::send_allocation_in_new_tlab(Klass*, HeapWordImpl**, unsigned long, unsigned long, JavaThread*)"},{"file":"libjvm.so","function":"MemAllocator::allocate() const"},{"file":"libjvm.so","function":"InstanceKlass::allocate_objArray(int, int, JavaThread*)"},{"file":"libjvm.so","function":"OptoRuntime::new_array_C(Klass*, int, JavaThread*)"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.59.0-SNAPSHOT~645ed325fc"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":"161958"},"sig_info":{"address":"0x00007f37a18bc187","name":"SIGSEGV","number":11},"timestamp":"2023-10-17T20:25:14+08:00","uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}
+{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"SIGSEGV","message":"Process terminated by signal SIGSEGV","source_type":"Crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"function":"vframeStreamForte::forte_next()","path":"libjvm.so","relative_address":"0x6b7187"},{"function":"forte_fill_call_trace_given_top(JavaThread*, ASGCT_CallTrace*, int, frame)","path":"libjvm.so","relative_address":"0x6b79ad"},{"function":"AsyncGetCallTrace","path":"libjvm.so","relative_address":"0x6b8123"},{"function":"Profiler::getJavaTraceAsync(void*, ASGCT_CallFrame*, int, StackContext*, bool*)","path":"libjavaProfiler.so","relative_address":"0x146c8"},{"function":"Profiler::recordSample(void*, unsigned long long, int, int, Event*)","path":"libjavaProfiler.so","relative_address":"0x1c18d"},{"function":"WallClock::sharedSignalHandler(int, siginfo_t*, void*)","path":"libjavaProfiler.so","relative_address":"0x1d128"},{"path":"libpthread.so.0","relative_address":"0x12cf0"},{"function":"JfrStackTrace::record_safe(JavaThread*, int)","path":"libjvm.so","relative_address":"0x85f9e9"},{"function":"JfrStackTraceRepository::record_for_leak_profiler(JavaThread*, int)","path":"libjvm.so","relative_address":"0x861650"},{"function":"ObjectSampler::sample(HeapWordImpl**, unsigned long, JavaThread*)","path":"libjvm.so","relative_address":"0xb1ecba"},{"function":"JfrAllocationTracer::JfrAllocationTracer(Klass const*, HeapWordImpl**, unsigned long, bool, JavaThread*)","path":"libjvm.so","relative_address":"0x81abee"},{"function":"AllocTracer::send_allocation_in_new_tlab(Klass*, HeapWordImpl**, unsigned long, unsigned long, JavaThread*)","path":"libjvm.so","relative_address":"0x3afa20"},{"function":"MemAllocator::allocate() const","path":"libjvm.so","relative_address":"0xa8f8ed"},{"function":"InstanceKlass::allocate_objArray(int, int, JavaThread*)","path":"libjvm.so","relative_address":"0x7e443c"},{"function":"OptoRuntime::new_array_C(Klass*, int, JavaThread*)","path":"libjvm.so","relative_address":"0xbe1b59"},{"function":"~RuntimeStub::_new_array_Java"},{"function":"java.util.HashMap.resize()[Ljava/util/HashMap$Node;"},{"function":"java.util.HashMap.putVal(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/lang/Object;"},{"function":"java.util.HashSet.add(Ljava/lang/Object;)Z"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.doTransform(Lorg/w3c/dom/Node;Ljava/io/PrintStream;IZZZZZ)V"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.doTransform(Lorg/w3c/dom/Node;Ljava/io/PrintStream;IZZZZZ)V"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.doTransform(Lorg/w3c/dom/Node;Ljava/io/PrintStream;IZZZZZ)V"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.doTransform(Lorg/w3c/dom/Node;Ljava/io/PrintStream;IZZZZZ)V"},{"function":"com.REDACT_THIS.util.dom.DOMUtil.partToString(Lorg/w3c/dom/Node;)Ljava/lang/String;"},{"function":"com.REDACT_THIS.message.impl.MessagePartDOMExt.writeExternal(Ljava/io/ObjectOutput;)V"},{"function":"java.io.ObjectOutputStream.writeOrdinaryObject(Ljava/lang/Object;Ljava/io/ObjectStreamClass;Z)V"},{"function":"java.io.ObjectOutputStream.writeObject0(Ljava/lang/Object;Z)V"},{"function":"jdk.internal.reflect.GeneratedMethodAccessor173.invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"},{"function":"java.io.ObjectOutputStream.writeSerialData(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V"},{"function":"java.io.ObjectOutputStream.writeOrdinaryObject(Ljava/lang/Object;Ljava/io/ObjectStreamClass;Z)V"},{"function":"java.io.ObjectOutputStream.writeObject0(Ljava/lang/Object;Z)V"},{"function":"com.REDACT_THIS.message.impl.Message.writeExternal(Ljava/io/ObjectOutput;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.ExternalizableConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshallField(Lcom/REDACT_THIS/xstream/converters/MarshallingContext;Ljava/lang/Object;Ljava/lang/reflect/Field;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshallField(Lcom/REDACT_THIS/xstream/converters/MarshallingContext;Ljava/lang/Object;Ljava/lang/reflect/Field;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshallField(Lcom/REDACT_THIS/xstream/converters/MarshallingContext;Ljava/lang/Object;Ljava/lang/reflect/Field;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshallField(Lcom/REDACT_THIS/xstream/converters/MarshallingContext;Ljava/lang/Object;Ljava/lang/reflect/Field;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.converters.reflection.AbstractReflectionConverter.marshal(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/io/HierarchicalStreamWriter;Lcom/REDACT_THIS/xstream/converters/MarshallingContext;)V"},{"function":"com.REDACT_THIS.xstream.core.TreeMarshaller.convertAnother(Ljava/lang/Object;Lcom/REDACT_THIS/xstream/converters/Converter;)V"},{"function":"com.REDACT_THIS.xstream.XStream.toXML(Ljava/lang/Object;Ljava/io/OutputStream;)V"},{"function":"com.REDACT_THIS.recover.impl.fs.FSJobSerializer.serializeJob(Ljava/lang/String;Lcom/REDACT_THIS/recover/impl/GenericRecoveryJob;)V"},{"function":"com.REDACT_THIS.recover.impl.db.RecoveryPoolDBImpl.specificSave(Lcom/REDACT_THIS/recover/spi/RecoveryJob;)V"},{"function":"com.REDACT_THIS.recover.impl.REDACT_THIS.saveJob(Lcom/REDACT_THIS/recover/spi/RecoveryJob;ILjava/lang/String;)V"},{"function":"com.REDACT_THIS.frame.core.FrameWorkListener.callbackNewData(Lcom/REDACT_THIS/frame/header/GenericTask;Ljava/util/concurrent/ExecutorService;)V"},{"function":"com.REDACT_THIS.frame.connection.direct.DirectModeMessageListener.onMessage(Ljavax/jms/Message;)V"},{"function":"datadog.trace.instrumentation.jms.DatadogMessageListener.onMessage(Ljavax/jms/Message;)V"},{"function":"org.apache.activemq.artemis.jms.client.JMSMessageListenerWrapper.onMessage(Lorg/apache/activemq/artemis/api/core/client/ClientMessage;)V"},{"function":"org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl$Runner.run()V"},{"function":"org.apache.activemq.artemis.utils.actors.ProcessorBase$$Lambda$2929+0x00000007ce44f8f8.run()V"},{"function":"java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V"},{"function":"java.util.concurrent.ThreadPoolExecutor$Worker.run()V"},{"function":"org.apache.activemq.artemis.utils.ActiveMQThreadFactory$1.run()V","line":44},{"function":"~StubRoutines::call_stub"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.59.0-SNAPSHOT~896c1ad986"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":161958},"sig_info":{"si_addr":"0x0000000000000070","si_code":1,"si_code_human_readable":"SEGV_MAPERR","si_signo":11,"si_signo_human_readable":"SIGSEGV"},"timestamp":"2023-10-17T20:25:14+08:00","uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}
diff --git a/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample_oom.txt b/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample_oom.txt
index 3f9791eff17..acc570a28d9 100644
--- a/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample_oom.txt
+++ b/dd-java-agent/agent-crashtracking/src/test/resources/golden/telemetry/sample_oom.txt
@@ -1 +1 @@
-{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"OutOfMemory","message":"Java heap space","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libjvm.dylib","function":"VMError::report_and_die(int, char const*, char const*, char*, Thread*, unsigned char*, void*, void*, char const*, int, unsigned long)"},{"file":"libjvm.dylib","function":"report_fatal(VMErrorType, char const*, int, char const*, ...)"},{"file":"libjvm.dylib","function":"report_java_out_of_memory(char const*)"},{"file":"libjvm.dylib","function":"MemAllocator::Allocation::check_out_of_memory()"},{"file":"libjvm.dylib","function":"MemAllocator::allocate() const"},{"file":"libjvm.dylib","function":"CollectedHeap::array_allocate(Klass*, unsigned long, int, bool, JavaThread*)"},{"file":"libjvm.dylib","function":"OptoRuntime::new_array_C(Klass*, int, JavaThread*)"},{"function":"Java"},{"function":" ~RuntimeStub::_new_array_Java 0x00000001124cb638"},{"function":"java.nio.ByteBuffer.allocate(I)Ljava/nio/ByteBuffer;"},{"function":"datadog.communication.serialization.FlushingBuffer.(ILdatadog/communication/serialization/ByteBufferConsumer;)V","line":6},{"function":"datadog.trace.agent.common.writer.PayloadDispatcherImpl.selectMapper()V","line":126},{"function":"datadog.trace.agent.common.writer.PayloadDispatcherImpl.addTrace(Ljava/util/List;)V","line":1},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.onEvent(Ljava/lang/Object;)V","line":22},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.consumeFromPrimaryQueue()V","line":21},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.runDutyCycle()V","line":12},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.run()V","line":1},{"function":"java.lang.Thread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V"},{"function":"java.lang.Thread.run()V"},{"function":" ~StubRoutines::call_stub 0x00000001123b0140"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.57.0-SNAPSHOT~0882768757"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":"74045"},"timestamp":"2025-11-24T09:43:29+01:00","uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}
+{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"OutOfMemory","message":"Java heap space","source_type":"Crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"function":"VMError::report_and_die(int, char const*, char const*, char*, Thread*, unsigned char*, void*, void*, char const*, int, unsigned long)","path":"libjvm.dylib","relative_address":"0xaa5f24"},{"function":"report_fatal(VMErrorType, char const*, int, char const*, ...)","path":"libjvm.dylib","relative_address":"0x314c74"},{"function":"report_java_out_of_memory(char const*)","path":"libjvm.dylib","relative_address":"0x315248"},{"function":"MemAllocator::Allocation::check_out_of_memory()","path":"libjvm.dylib","relative_address":"0x7d9764"},{"function":"MemAllocator::allocate() const","path":"libjvm.dylib","relative_address":"0x7da2f4"},{"function":"CollectedHeap::array_allocate(Klass*, unsigned long, int, bool, JavaThread*)","path":"libjvm.dylib","relative_address":"0x2c3de4"},{"function":"OptoRuntime::new_array_C(Klass*, int, JavaThread*)","path":"libjvm.dylib","relative_address":"0x8ea9ec"},{"function":"~RuntimeStub::_new_array_Java","relative_address":"0x00000001124cb638"},{"function":"java.nio.ByteBuffer.allocate(I)Ljava/nio/ByteBuffer;"},{"function":"datadog.communication.serialization.FlushingBuffer.(ILdatadog/communication/serialization/ByteBufferConsumer;)V","line":6},{"function":"datadog.trace.agent.common.writer.PayloadDispatcherImpl.selectMapper()V","line":126},{"function":"datadog.trace.agent.common.writer.PayloadDispatcherImpl.addTrace(Ljava/util/List;)V","line":1},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.onEvent(Ljava/lang/Object;)V","line":22},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.consumeFromPrimaryQueue()V","line":21},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.runDutyCycle()V","line":12},{"function":"datadog.trace.agent.common.writer.TraceProcessingWorker$TraceSerializingHandler.run()V","line":1},{"function":"java.lang.Thread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V"},{"function":"java.lang.Thread.run()V"},{"function":"~StubRoutines::call_stub","relative_address":"0x00000001123b0140"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.59.0-SNAPSHOT~896c1ad986"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":74045},"timestamp":"2025-11-24T09:43:29+01:00","uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}