Skip to content

Commit f76674e

Browse files
bric3devflow.devflow-routing-intake
andauthored
Reports loaded libraries in crashtracking reports (#11000)
chore(crashtracking): Better json assertions feat(crashtracking): Reports dynamic libs in the error.files section RFC: https://github.com/DataDog/libdatadog/blob/main/docs/RFCs/0011-crashtracker-structured-log-format-V1_X.md Note that on BSD / macOs the loaded libs is not coming from procfs. Merge branch 'master' into bdu/proc-memory-mappings Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent c1f2818 commit f76674e

File tree

14 files changed

+1342
-35
lines changed

14 files changed

+1342
-35
lines changed

dd-java-agent/agent-crashtracking/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ dependencies {
2525

2626
testImplementation libs.bundles.junit5
2727
testImplementation libs.bundles.mockito
28+
testImplementation libs.assertj.core
29+
testImplementation libs.json.unit.assertj
2830
testImplementation libs.jackson.databind
2931
testImplementation libs.testcontainers
3032
testImplementation group: 'com.squareup.okhttp3', name: 'mockwebserver', version: libs.versions.okhttp.legacy.get()

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/CrashUploader.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,18 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
586586
writer.endObject();
587587
writer.endObject();
588588
}
589+
// files (e.g. /proc/self/maps or dynamic_libraries)
590+
if (payload.files != null) {
591+
writer.name("files");
592+
writer.beginObject();
593+
writer.name(payload.files.name);
594+
writer.beginArray();
595+
for (String fileLine : payload.files.lines) {
596+
writer.value(fileLine);
597+
}
598+
writer.endArray();
599+
writer.endObject();
600+
}
589601
writer.endObject();
590602
}
591603
return RequestBody.create(APPLICATION_JSON, buf.readByteString());

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/CrashLog.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public final class CrashLog {
1313
public static final JsonAdapter<CrashLog> ADAPTER;
1414

1515
static {
16-
Moshi moshi = new Moshi.Builder().build();
16+
Moshi moshi = new Moshi.Builder().add(new DynamicLibs.JsonAdapter()).build();
1717
ADAPTER = moshi.adapter(CrashLog.class);
1818
}
1919

@@ -41,6 +41,12 @@ public final class CrashLog {
4141

4242
public final Experimental experimental;
4343

44+
/**
45+
* Useful files for triage and debugging (e.g. {@code /proc/self/maps}, {@code
46+
* dynamic_libraries}).
47+
*/
48+
public final DynamicLibs files;
49+
4450
public CrashLog(
4551
String uuid,
4652
boolean incomplete,
@@ -61,6 +67,7 @@ public CrashLog(
6167
procInfo,
6268
sigInfo,
6369
dataSchemaVersion,
70+
null,
6471
null);
6572
}
6673

@@ -74,7 +81,8 @@ public CrashLog(
7481
ProcInfo procInfo,
7582
SigInfo sigInfo,
7683
String dataSchemaVersion,
77-
Experimental experimental) {
84+
Experimental experimental,
85+
DynamicLibs files) {
7886
this.uuid = uuid != null ? uuid : RandomUtils.randomUUID().toString();
7987
this.incomplete = incomplete;
8088
this.timestamp = timestamp;
@@ -85,6 +93,7 @@ public CrashLog(
8593
this.sigInfo = sigInfo;
8694
this.dataSchemaVersion = dataSchemaVersion;
8795
this.experimental = experimental;
96+
this.files = files;
8897
}
8998

9099
public String toJson() {
@@ -113,7 +122,8 @@ public boolean equals(Object o) {
113122
&& Objects.equals(procInfo, crashLog.procInfo)
114123
&& Objects.equals(sigInfo, crashLog.sigInfo)
115124
&& Objects.equals(dataSchemaVersion, crashLog.dataSchemaVersion)
116-
&& Objects.equals(experimental, crashLog.experimental);
125+
&& Objects.equals(experimental, crashLog.experimental)
126+
&& Objects.equals(files, crashLog.files);
117127
}
118128

119129
@Override
@@ -129,7 +139,8 @@ public int hashCode() {
129139
sigInfo,
130140
version,
131141
dataSchemaVersion,
132-
experimental);
142+
experimental,
143+
files);
133144
}
134145

135146
public boolean equalsForTest(Object o) {
@@ -149,6 +160,7 @@ public boolean equalsForTest(Object o) {
149160
&& Objects.equals(procInfo, crashLog.procInfo)
150161
&& Objects.equals(sigInfo, crashLog.sigInfo)
151162
&& Objects.equals(dataSchemaVersion, crashLog.dataSchemaVersion)
152-
&& Objects.equals(experimental, crashLog.experimental);
163+
&& Objects.equals(experimental, crashLog.experimental)
164+
&& Objects.equals(files, crashLog.files);
153165
}
154166
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package datadog.crashtracking.dto;
2+
3+
import com.squareup.moshi.FromJson;
4+
import com.squareup.moshi.ToJson;
5+
import java.util.Collections;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
public class DynamicLibs {
10+
public final String name;
11+
public final List<String> lines;
12+
13+
public DynamicLibs(String name, List<String> lines) {
14+
this.name = name;
15+
this.lines = lines;
16+
}
17+
18+
public static class JsonAdapter {
19+
@ToJson
20+
Map<String, List<String>> toJson(DynamicLibs dynamicLibs) {
21+
return Collections.singletonMap(dynamicLibs.name, dynamicLibs.lines);
22+
}
23+
24+
@FromJson
25+
DynamicLibs fromJson(Map<String, List<String>> map) {
26+
Map.Entry<String, List<String>> entry = map.entrySet().iterator().next();
27+
return new DynamicLibs(entry.getKey(), entry.getValue());
28+
}
29+
}
30+
}

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/parsers/HotspotCrashLogParser.java

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import datadog.crashtracking.buildid.BuildIdCollector;
77
import datadog.crashtracking.buildid.BuildInfo;
88
import datadog.crashtracking.dto.CrashLog;
9+
import datadog.crashtracking.dto.DynamicLibs;
910
import datadog.crashtracking.dto.ErrorData;
1011
import datadog.crashtracking.dto.Experimental;
1112
import datadog.crashtracking.dto.Metadata;
@@ -341,6 +342,8 @@ public CrashLog parse(String uuid, String crashLog) {
341342
boolean incomplete = false;
342343
String oomMessage = null;
343344
Map<String, String> registers = null;
345+
List<String> dynamicLibraryLines = null;
346+
String dynamicLibraryKey = null;
344347

345348
String[] lines = NEWLINE_SPLITTER.split(crashLog);
346349
outer:
@@ -449,15 +452,21 @@ public CrashLog parse(String uuid, String crashLog) {
449452
case DYNAMIC_LIBRARIES:
450453
if (line.isEmpty()) {
451454
state = State.SEEK_DYNAMIC_LIBRARIES;
452-
}
453-
final Matcher matcher = DYNAMIC_LIBS_PATH_PARSER.matcher(line);
454-
if (matcher.matches()) {
455-
final String pathString = matcher.group(1);
456-
if (pathString != null && !pathString.isEmpty()) {
457-
try {
458-
final Path path = Paths.get(pathString);
459-
buildIdCollector.resolveBuildId(path);
460-
} catch (InvalidPathException ignored) {
455+
} else {
456+
if (dynamicLibraryKey == null) {
457+
dynamicLibraryKey = detectDynamicLibrariesKey(line);
458+
dynamicLibraryLines = new ArrayList<>();
459+
}
460+
final Matcher matcher = DYNAMIC_LIBS_PATH_PARSER.matcher(line);
461+
if (matcher.matches()) {
462+
final String pathString = matcher.group(1);
463+
if (pathString != null && !pathString.isEmpty()) {
464+
dynamicLibraryLines.add(line);
465+
try {
466+
final Path path = Paths.get(pathString);
467+
buildIdCollector.resolveBuildId(path);
468+
} catch (InvalidPathException ignored) {
469+
}
461470
}
462471
}
463472
}
@@ -545,6 +554,10 @@ public CrashLog parse(String uuid, String crashLog) {
545554
ProcInfo procInfo = parsedPid != null ? new ProcInfo(parsedPid) : null;
546555
Experimental experimental =
547556
(registers != null && !registers.isEmpty()) ? new Experimental(registers) : null;
557+
DynamicLibs files =
558+
(dynamicLibraryLines != null && !dynamicLibraryLines.isEmpty())
559+
? new DynamicLibs(dynamicLibraryKey, dynamicLibraryLines)
560+
: null;
548561
return new CrashLog(
549562
uuid,
550563
incomplete,
@@ -555,7 +568,8 @@ public CrashLog parse(String uuid, String crashLog) {
555568
procInfo,
556569
sigInfo,
557570
"1.0",
558-
experimental);
571+
experimental,
572+
files);
559573
}
560574

561575
static String dateTimeToISO(String datetime) {
@@ -572,6 +586,40 @@ static String dateTimeToISO(String datetime) {
572586
}
573587
}
574588

589+
/**
590+
* Detects whether the Dynamic libraries section comes from Linux {@code /proc/self/maps} (address
591+
* range format {@code addr-addr perms ...}) or from the BSD/macOS dyld callback (format {@code
592+
* 0xaddr\tpath}). Returns the appropriate map key.
593+
*/
594+
// The "Dynamic libraries:" section is written by os::print_dll_info(), whose implementation
595+
// differs by platform:
596+
//
597+
// Linux
598+
// -----
599+
// This reads `/proc/{tid}/maps` verbatim via _print_ascii_file(), producing the usual
600+
// `/proc/self/maps` format:
601+
//
602+
// "addr-addr perms offset dev:inode [path]"
603+
//
604+
// Mainline:
605+
// https://github.com/openjdk/jdk/blob/783f8f1adc4ea3ef7fd4c5ca5473aad76dfc7ed1/src/hotspot/os/linux/os_linux.cpp#L2086-L2099
606+
//
607+
// BSD/macOS
608+
// ---------
609+
// This relies on `_dyld_image_count()`/`_dyld_get_image_name()` (on macOS) or
610+
// `dlinfo(RTLD_DI_LINKMAP)` (on FreeBSD/OpenBSD) via a callback, producing a simpler format:
611+
//
612+
// "0xaddr\tpath"
613+
//
614+
// which lacks much of the information found in Linux's `/proc/self/maps`.
615+
// Mainline:
616+
// https://github.com/openjdk/jdk/blob/783f8f1adc4ea3ef7fd4c5ca5473aad76dfc7ed1/src/hotspot/os/bsd/os_bsd.cpp#L1382-L1387
617+
static String detectDynamicLibrariesKey(String firstLine) {
618+
int dash = firstLine.indexOf('-');
619+
int space = firstLine.indexOf(' ');
620+
return (dash > 0 && space > 0 && dash < space) ? "/proc/self/maps" : "dynamic_libraries";
621+
}
622+
575623
static Integer safelyParseInt(String value) {
576624
if (value == null) {
577625
return null;

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/parsers/J9JavacoreParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,8 @@ public CrashLog parse(String uuid, String javacoreContent) {
303303
procInfo,
304304
sigInfo,
305305
"1.0",
306-
experimental);
306+
experimental,
307+
null);
307308
}
308309

309310
private static Integer safelyParseInt(String value) {

dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/CrashUploaderTest.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static datadog.crashtracking.CrashUploader.HEADER_DD_EVP_SUBDOMAIN;
44
import static datadog.crashtracking.CrashUploader.HEADER_DD_TELEMETRY_API_VERSION;
55
import static datadog.crashtracking.CrashUploader.TELEMETRY_API_VERSION;
6+
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
67
import static org.junit.jupiter.api.Assertions.assertEquals;
78
import static org.junit.jupiter.api.Assertions.assertFalse;
89
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -292,9 +293,9 @@ public void testTelemetryHappyPath(String log) throws Exception {
292293
String message = event.get("payload").get(0).get("message").asText();
293294
CrashLog extracted = CrashLog.fromJson(message);
294295

295-
assertTrue(
296-
expected.equalsForTest(extracted),
297-
() -> "Expected: " + expected.toJson() + "\nbut got: " + extracted.toJson());
296+
assertThatJson(extracted.toJson())
297+
.whenIgnoringPaths("os_info", "metadata")
298+
.isEqualTo(expected.toJson());
298299
assertEquals("severity:crash", event.get("payload").get(0).get("tags").asText());
299300
assertCommonPayload(event);
300301
}
@@ -356,19 +357,8 @@ public void testErrorTrackingHappyPath(String log) throws Exception {
356357
assertTrue(ddtags.contains("runtime_name:"));
357358

358359
// assert platform independent equality
359-
assertEquals(
360-
expected,
361-
extracted,
362-
() -> {
363-
try {
364-
return "Expected: "
365-
+ mapper.writeValueAsString(expected)
366-
+ "\nbut got: "
367-
+ mapper.writeValueAsString(extracted);
368-
} catch (JsonProcessingException e) {
369-
throw new RuntimeException(e);
370-
}
371-
});
360+
assertThatJson(mapper.writeValueAsString(extracted))
361+
.isEqualTo(mapper.writeValueAsString(expected));
372362
}
373363

374364
private void assertCommonHeader(JsonNode event) {

0 commit comments

Comments
 (0)