Summary
ddprof fails to symbolicate any JIT frames from a /tmp/perf-<pid>.map file whose
address/size fields are 0x-prefixed — the format jcmd <pid> Compiler.perfmap emits
on OpenJDK 25. Every line of the map is rejected at parse time, the runtime symbol map
stays empty, and all JIT-compiled Java frames render as Anonymous in profiles. The
failure is silent above debug log level.
Environment
- ddprof v0.26.0 (the parser is unchanged on current
main)
- OpenJDK 25 (
java-25-openjdk), Linux container
- ddprof in wrapper mode (
ddprof --preset alloc_live_heap ... /usr/bin/java ...),
JVM running with -XX:+PreserveFramePointer
- Perf map regenerated every 300s via
jcmd <pid> Compiler.perfmap
Reproduction
-
Run a JVM under ddprof wrapper mode with any preset.
-
Generate the map with jcmd <pid> Compiler.perfmap. On JDK 25 the file contains
(captured from our production pod):
0x00007f6cc9200100 0x0000000000000368 java.lang.Object jdk.internal.misc.Unsafe.getReferenceVolatile(java.lang.Object, long)
0x00007f6cc9200580 0x0000000000000178 int jdk.internal.misc.Unsafe.getInt(java.lang.Object, long)
-
Observe profiles: JIT regions never resolve; all such frames show as Anonymous.
Rewriting the same file to bare hex (7f6cc9200100 368 <symbol>) makes
symbolication work immediately.
Root cause
In src/runtime_symbol_lookup.cc, each line is parsed with:
sscanf(line, "%16s %8s %300[^\t\n]", address_buff, size_buff, buffer)
A 0x-prefixed 64-bit address token is 18 characters (0x + 16 hex digits):
%16s truncates the address to 0x00007f6cc92001 (corrupting it), and
%8s then consumes the leftover 00 of the address token as the size field,
so the real size is never read and code_size parses to 0.
The subsequent validity check in insert_or_replace() (!address || !code_size)
rejects the entry. This happens for every line, so the map is 100% discarded.
Because a failed lookup only logs at debug, nothing surfaces at default/warn levels —
we confirmed 72h of profiles with zero resolved Java JIT frames before finding this.
Suggested fix
Widen the field buffers (e.g. %18s) and parse both fields with
strtoul(str, NULL, 16), which accepts the 0x prefix natively; the same code path
then handles both the classic bare-hex format and the JDK 25 output.
A warn-level log when a large fraction of perf-map lines is rejected would also make
this failure mode diagnosable.
Workaround
Post-process the JDK dump before ddprof reads it, publishing atomically:
jcmd "$pid" Compiler.perfmap "/tmp/perf-$pid.map.jvm" &&
gawk '{ a = strtonum($1 ~ /^0x/ ? $1 : "0x" $1);
s = strtonum($2 ~ /^0x/ ? $2 : "0x" $2);
$1 = ""; $2 = ""; sub(/^ */, "");
printf "%x %x %s\n", a, s, $0 }' "/tmp/perf-$pid.map.jvm" \
> "/tmp/perf-$pid.map.tmp" &&
mv "/tmp/perf-$pid.map.tmp" "/tmp/perf-$pid.map"
Summary
ddproffails to symbolicate any JIT frames from a/tmp/perf-<pid>.mapfile whoseaddress/size fields are
0x-prefixed — the formatjcmd <pid> Compiler.perfmapemitson OpenJDK 25. Every line of the map is rejected at parse time, the runtime symbol map
stays empty, and all JIT-compiled Java frames render as
Anonymousin profiles. Thefailure is silent above debug log level.
Environment
main)java-25-openjdk), Linux containerddprof --preset alloc_live_heap ... /usr/bin/java ...),JVM running with
-XX:+PreserveFramePointerjcmd <pid> Compiler.perfmapReproduction
Run a JVM under ddprof wrapper mode with any preset.
Generate the map with
jcmd <pid> Compiler.perfmap. On JDK 25 the file contains(captured from our production pod):
Observe profiles: JIT regions never resolve; all such frames show as
Anonymous.Rewriting the same file to bare hex (
7f6cc9200100 368 <symbol>) makessymbolication work immediately.
Root cause
In
src/runtime_symbol_lookup.cc, each line is parsed with:A
0x-prefixed 64-bit address token is 18 characters (0x+ 16 hex digits):%16struncates the address to0x00007f6cc92001(corrupting it), and%8sthen consumes the leftover00of the address token as the size field,so the real size is never read and
code_sizeparses to0.The subsequent validity check in
insert_or_replace()(!address || !code_size)rejects the entry. This happens for every line, so the map is 100% discarded.
Because a failed lookup only logs at debug, nothing surfaces at default/warn levels —
we confirmed 72h of profiles with zero resolved Java JIT frames before finding this.
Suggested fix
Widen the field buffers (e.g.
%18s) and parse both fields withstrtoul(str, NULL, 16), which accepts the0xprefix natively; the same code paththen handles both the classic bare-hex format and the JDK 25 output.
A warn-level log when a large fraction of perf-map lines is rejected would also make
this failure mode diagnosable.
Workaround
Post-process the JDK dump before ddprof reads it, publishing atomically: