Skip to content

perf-map parser silently rejects all entries when addresses are 0x-prefixed (JDK 25 jcmd Compiler.perfmap format) — all JIT frames become Anonymous #560

Description

@jlewis-spotnana

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

  1. Run a JVM under ddprof wrapper mode with any preset.

  2. 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)
    
  3. 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"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions