Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static CrashLog parse(String uuid, String content) {
if (isJ9Javacore(content)) {
return fromJ9Javacore(uuid, content);
}
return fromHotspotCrashLog(uuid, content);
return new HotspotCrashLogParser().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 @@ -579,6 +579,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 @@ -590,6 +591,16 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
}
writer.endObject();
}
if (uploaderSettings.isExtendedInfoEnabled()
&& 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 (uploaderSettings.isExtendedInfoEnabled()
&& payload.experimental.runtimeArgs != null) {
writer.name("runtime_args");
Expand Down
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 @@ -62,6 +62,7 @@ enum State {
SUMMARY,
THREAD,
STACKTRACE,
REGISTER_TO_MEMORY_MAPPING,
REGISTERS,
PROCESS,
VM_ARGUMENTS,
Expand Down Expand Up @@ -95,12 +96,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 +354,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 +418,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 +437,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 +445,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 +534,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 +598,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 (!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 +641,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