Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public static class StoredConfig {
final String reportUUID;
final boolean agentless;
final boolean sendToErrorTracking;
final boolean registerMappingEnabled;

StoredConfig(
String reportUUID,
Expand All @@ -45,7 +46,8 @@ public static class StoredConfig {
String processTags,
String runtimeId,
boolean agentless,
boolean sendToErrorTracking) {
boolean sendToErrorTracking,
boolean registerMappingEnabled) {
this.service = service;
this.env = env;
this.version = version;
Expand All @@ -55,6 +57,11 @@ public static class StoredConfig {
this.reportUUID = reportUUID;
this.agentless = agentless;
this.sendToErrorTracking = sendToErrorTracking;
this.registerMappingEnabled = registerMappingEnabled;
}

public CrashUploaderSettings toCrashUploaderSettings() {
return new CrashUploaderSettings(registerMappingEnabled);
}

public static class Builder {
Expand All @@ -67,6 +74,7 @@ public static class Builder {
String reportUUID;
boolean agentless;
boolean sendToErrorTracking;
boolean registerMappingEnabled;

public Builder(Config config) {
// get sane defaults
Expand All @@ -77,6 +85,7 @@ public Builder(Config config) {
this.reportUUID = RandomUtils.randomUUID().toString();
this.agentless = config.isCrashTrackingAgentless();
this.sendToErrorTracking = config.isCrashTrackingErrorsIntakeEnabled();
this.registerMappingEnabled = config.isCrashTrackingExperimentalRegisterMappingEnabled();
}

public Builder service(String service) {
Expand Down Expand Up @@ -119,6 +128,11 @@ public Builder agentless(boolean agentless) {
return this;
}

public Builder registerMappingEnabled(boolean registerMappingEnabled) {
this.registerMappingEnabled = registerMappingEnabled;
return this;
}

// @VisibleForTesting
Builder reportUUID(String reportUUID) {
this.reportUUID = reportUUID;
Expand All @@ -135,7 +149,8 @@ public StoredConfig build() {
processTags,
runtimeId,
agentless,
sendToErrorTracking);
sendToErrorTracking,
registerMappingEnabled);
}
}
}
Expand Down Expand Up @@ -194,6 +209,10 @@ static void writeConfigToFile(Config config, Path cfgPath, String... additionalE
writeEntry(bw, "java_home", SystemProperties.get("java.home"));
writeEntry(bw, "agentless", Boolean.toString(config.isCrashTrackingAgentless()));
writeEntry(bw, "upload_to_et", Boolean.toString(config.isCrashTrackingErrorsIntakeEnabled()));
writeEntry(
bw,
"register_mapping",
Boolean.toString(config.isCrashTrackingExperimentalRegisterMappingEnabled()));

Runtime.getRuntime()
.addShutdownHook(
Expand Down Expand Up @@ -257,6 +276,9 @@ public static StoredConfig readConfig(Config config, Path scriptPath) {
case "upload_to_et":
cfgBuilder.sendToErrorTracking(Boolean.parseBoolean(value));
break;
case "register_mapping":
cfgBuilder.registerMappingEnabled(Boolean.parseBoolean(value));
break;
default:
// ignore
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,18 @@ public static CrashLog fromJ9Javacore(String uuid, String javacoreContent) {
* </ul>
*/
public static CrashLog parse(String uuid, String content) {
return parse(uuid, content, new CrashUploaderSettings(true));
}

/**
* Auto-detect crash log format and parse accordingly, using the provided settings to control
* which sections are included in the result.
*/
public static CrashLog parse(String uuid, String content, CrashUploaderSettings settings) {
if (isJ9Javacore(content)) {
return fromJ9Javacore(uuid, content);
}
return fromHotspotCrashLog(uuid, content);
return new HotspotCrashLogParser(settings).parse(uuid, content);
}

/** Check if the content appears to be a J9 javacore file. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ void remoteUpload(
final String uuid = storedConfig.reportUUID;
try {
// Auto-detect crash log format (HotSpot hs_err or J9 javacore)
CrashLog crashLog = CrashLogParser.parse(uuid, fileContent);
CrashLog crashLog =
CrashLogParser.parse(uuid, fileContent, storedConfig.toCrashUploaderSettings());
if (sendToTelemetry) {
uploadToTelemetry(crashLog);
}
Expand Down Expand Up @@ -577,6 +578,7 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
// experimental
if (payload.experimental != null
&& (payload.experimental.ucontext != null
|| payload.experimental.registerToMemoryMapping != null
|| payload.experimental.runtimeArgs != null)) {
writer.name("experimental");
writer.beginObject();
Expand All @@ -588,6 +590,15 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
}
writer.endObject();
}
if (payload.experimental.registerToMemoryMapping != null) {
writer.name("register_to_memory_mapping");
writer.beginObject();
for (Map.Entry<String, String> entry :
payload.experimental.registerToMemoryMapping.entrySet()) {
writer.name(entry.getKey()).value(entry.getValue());
}
writer.endObject();
}
if (payload.experimental.runtimeArgs != null) {
writer.name("runtime_args");
writer.beginArray();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package datadog.crashtracking;

/**
* Crash-tracking-specific settings controlling which sections are included in parsed crash reports.
*/
public final class CrashUploaderSettings {

private final boolean registerMappingEnabled;

public CrashUploaderSettings(boolean registerMappingEnabled) {
this.registerMappingEnabled = registerMappingEnabled;
}

/** Whether the register-to-memory mapping section should be included in parsed crash reports. */
public boolean isRegisterMappingEnabled() {
return registerMappingEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,40 @@
public final class Experimental {
public final Map<String, String> ucontext;

@Json(name = "register_to_memory_mapping")
public final Map<String, String> registerToMemoryMapping;

@Json(name = "runtime_args")
public final List<String> runtimeArgs;

public Experimental(Map<String, String> ucontext) {
this(ucontext, null);
this(ucontext, null, null);
}

public Experimental(Map<String, String> ucontext, List<String> runtimeArgs) {
this(ucontext, null, runtimeArgs);
}

public Experimental(
Map<String, String> ucontext,
Map<String, String> registerToMemoryMapping,
List<String> runtimeArgs) {
this.ucontext = ucontext;
this.registerToMemoryMapping = registerToMemoryMapping;
this.runtimeArgs = runtimeArgs;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof Experimental)) return false;
Experimental that = (Experimental) o;
return Objects.equals(ucontext, that.ucontext) && Objects.equals(runtimeArgs, that.runtimeArgs);
return Objects.equals(ucontext, that.ucontext)
&& Objects.equals(registerToMemoryMapping, that.registerToMemoryMapping)
&& Objects.equals(runtimeArgs, that.runtimeArgs);
}

@Override
public int hashCode() {
return Objects.hash(ucontext, runtimeArgs);
return Objects.hash(ucontext, registerToMemoryMapping, runtimeArgs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;

import datadog.common.version.VersionInfo;
import datadog.crashtracking.CrashUploaderSettings;
import datadog.crashtracking.buildid.BuildIdCollector;
import datadog.crashtracking.buildid.BuildInfo;
import datadog.crashtracking.dto.CrashLog;
Expand Down Expand Up @@ -62,6 +63,7 @@ enum State {
SUMMARY,
THREAD,
STACKTRACE,
REGISTER_TO_MEMORY_MAPPING,
REGISTERS,
PROCESS,
VM_ARGUMENTS,
Expand All @@ -71,9 +73,15 @@ enum State {
}

private State state = State.NEW;
private final CrashUploaderSettings settings;

public HotspotCrashLogParser() {
this(new CrashUploaderSettings(true));
}

public HotspotCrashLogParser(CrashUploaderSettings settings) {
this.buildIdCollector = new BuildIdCollector();
this.settings = settings;
}

private static final Pattern PLUS_SPLITTER = Pattern.compile("\\+");
Expand All @@ -95,12 +103,15 @@ public HotspotCrashLogParser() {
// per line.
private static final Pattern REGISTER_ENTRY_PARSER =
Pattern.compile("([A-Za-z][A-Za-z0-9]*)\\s*=\\s*(0x[0-9a-fA-F]+)");
private static final Pattern REGISTER_TO_MEMORY_MAPPING_PARSER =
Pattern.compile("^\\s*([A-Za-z][A-Za-z0-9]*)\\s*=");
// Used for the REGISTERS-state exit condition only: the register name must start the line
// (after optional whitespace). This prevents lines like "Top of Stack: (sp=0x...)" and
// "Instructions: (pc=0x...)" from being mistaken for register entries by REGISTER_ENTRY_PARSER's
// find(), which would otherwise match the lowercase "sp"/"pc" tokens embedded in those lines.
private static final Pattern REGISTER_LINE_START =
Pattern.compile("^\\s*[A-Za-z][A-Za-z0-9]*\\s*=\\s*0x");
private static final Pattern SUBSECTION_TITLE = Pattern.compile("^\\s*[A-Za-z][\\w ]*:.+$");
private static final Pattern COMPILED_JAVA_ADDRESS_PARSER =
Pattern.compile("@\\s+(0x[0-9a-fA-F]+)\\s+\\[(0x[0-9a-fA-F]+)\\+(0x[0-9a-fA-F]+)\\]");

Expand Down Expand Up @@ -350,10 +361,14 @@ public CrashLog parse(String uuid, String crashLog) {
String datetimeRaw = null;
boolean incomplete = false;
String oomMessage = null;
Map<String, String> registers = null;
Map<String, String> registers = new LinkedHashMap<>();
Map<String, String> registerToMemoryMapping = new LinkedHashMap<>();
String currentRegisterToMemoryMapping = "";
List<String> runtimeArgs = null;
List<String> dynamicLibraryLines = null;
String dynamicLibraryKey = null;
boolean previousLineBlank = false;
State nextThreadSectionState = null;

String[] lines = NEWLINE_SPLITTER.split(crashLog);
outer:
Expand Down Expand Up @@ -410,7 +425,10 @@ public CrashLog parse(String uuid, String crashLog) {
}
break;
case STACKTRACE:
if (line.startsWith("siginfo:")) {
nextThreadSectionState = nextThreadSectionState(line, previousLineBlank);
if (nextThreadSectionState != null) {
state = nextThreadSectionState;
} else if (line.startsWith("siginfo:")) {
// spotless:off
// siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x70
// siginfo: si_signo: 11 (SIGSEGV), si_code: 0 (SI_USER), si_pid: 554848, si_uid: 1000
Expand All @@ -426,11 +444,6 @@ public CrashLog parse(String uuid, String crashLog) {
Integer siUid = safelyParseInt(siginfoMatcher.group(7));
sigInfo = new SigInfo(number, name, siCode, sigAction, address, siPid, siUid);
}
} else if (line.startsWith("Registers:")) {
registers = new LinkedHashMap<>();
state = State.REGISTERS;
} else if (line.contains("P R O C E S S")) {
state = State.PROCESS;
} else {
// Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
final StackFrame frame = parseLine(line);
Expand All @@ -439,8 +452,28 @@ public CrashLog parse(String uuid, String crashLog) {
}
}
break;
case REGISTER_TO_MEMORY_MAPPING:
nextThreadSectionState = nextThreadSectionState(line, previousLineBlank);
if (nextThreadSectionState != null) {
currentRegisterToMemoryMapping = "";
state = nextThreadSectionState;
} else if (!line.isEmpty()) {
final Matcher m = REGISTER_TO_MEMORY_MAPPING_PARSER.matcher(line);
if (m.lookingAt()) {
currentRegisterToMemoryMapping = m.group(1);
registerToMemoryMapping.put(
currentRegisterToMemoryMapping, line.substring(line.indexOf('=') + 1));
} else if (!currentRegisterToMemoryMapping.isEmpty()) {
registerToMemoryMapping.computeIfPresent(
currentRegisterToMemoryMapping, (key, value) -> value + "\n" + line);
}
}
break;
case REGISTERS:
if (!line.isEmpty() && !REGISTER_LINE_START.matcher(line).find()) {
nextThreadSectionState = nextThreadSectionState(line, previousLineBlank);
if (nextThreadSectionState != null) {
state = nextThreadSectionState;
} else if (!line.isEmpty() && !REGISTER_LINE_START.matcher(line).find()) {
// non-empty line that does not start with a register entry signals end of section
state = State.STACKTRACE;
} else {
Expand Down Expand Up @@ -508,6 +541,7 @@ public CrashLog parse(String uuid, String crashLog) {
// unexpected parser state; bail out
break outer;
}
previousLineBlank = line.isEmpty();
}

// PROCESS and SYSTEM sections are late enough that all critical data is captured
Expand Down Expand Up @@ -571,10 +605,16 @@ public CrashLog parse(String uuid, String crashLog) {
Metadata metadata = new Metadata("dd-trace-java", VersionInfo.VERSION, "java", null);
Integer parsedPid = safelyParseInt(pid);
ProcInfo procInfo = parsedPid != null ? new ProcInfo(parsedPid) : null;
Map<String, String> resolvedMapping = null;
if (settings.isRegisterMappingEnabled() && !registerToMemoryMapping.isEmpty()) {
registerToMemoryMapping.replaceAll((k, v) -> RedactUtils.redactRegisterToMemoryMapping(v));
resolvedMapping = registerToMemoryMapping;
}
Experimental experimental =
(registers != null && !registers.isEmpty())
!registers.isEmpty()
|| resolvedMapping != null
|| (runtimeArgs != null && !runtimeArgs.isEmpty())
? new Experimental(registers, runtimeArgs)
? new Experimental(registers, resolvedMapping, runtimeArgs)
: null;
DynamicLibs files =
(dynamicLibraryLines != null && !dynamicLibraryLines.isEmpty())
Expand Down Expand Up @@ -608,6 +648,25 @@ static String dateTimeToISO(String datetime) {
}
}

private static State nextThreadSectionState(String line, boolean previousLineBlank) {
if (line.startsWith("Register to memory mapping:")) {
return State.REGISTER_TO_MEMORY_MAPPING;
}
if (line.startsWith("Registers:")) {
return State.REGISTERS;
}
if (line.startsWith("siginfo:")) {
return null;
}
if (line.contains("P R O C E S S")) {
return State.PROCESS;
}
if (previousLineBlank && SUBSECTION_TITLE.matcher(line).matches()) {
return State.STACKTRACE;
}
return null;
}

/**
* Detects whether the Dynamic libraries section comes from Linux {@code /proc/self/maps} (address
* range format {@code addr-addr perms ...}) or from the BSD/macOS dyld callback (format {@code
Expand Down
Loading
Loading