8376264: Mixed jstack could not unwind optimized frame

This commit is contained in:
Yasumasa Suenaga 2026-01-25 00:07:55 +09:00
parent a3b1aa9f7d
commit 9a8c7ec857
7 changed files with 97 additions and 125 deletions

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2021, NTT DATA.
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, NTT DATA.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -219,16 +219,3 @@ JNIEXPORT jint JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_get
DwarfParser *parser = reinterpret_cast<DwarfParser *>(get_dwarf_context(env, this_obj));
return parser->get_bp_cfa_offset();
}
/*
* Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser
* Method: isBPOffsetAvailable
* Signature: ()Z
*/
extern "C"
JNIEXPORT jboolean JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_isBPOffsetAvailable
(JNIEnv *env, jobject this_obj) {
DwarfParser *parser = reinterpret_cast<DwarfParser *>(get_dwarf_context(env, this_obj));
return parser->is_bp_offset_available();
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, NTT DATA.
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, NTT DATA.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -99,12 +99,11 @@ bool DwarfParser::process_cie(unsigned char *start_of_entry, uint32_t id) {
// Clear state
_current_pc = 0L;
_cfa_reg = RSP;
_cfa_reg = MAX_VALUE;
_return_address_reg = RA;
_cfa_offset = 0;
_ra_cfa_offset = 0;
_bp_cfa_offset = 0;
_bp_offset_available = false;
_ra_cfa_offset = 8;
_bp_cfa_offset = INT_MAX;
parse_dwarf_instructions(0L, static_cast<uintptr_t>(-1L), end);
@ -119,8 +118,8 @@ void DwarfParser::parse_dwarf_instructions(uintptr_t begin, uintptr_t pc, const
/* for remember state */
enum DWARF_Register rem_cfa_reg = MAX_VALUE;
int rem_cfa_offset = 0;
int rem_ra_cfa_offset = 0;
int rem_bp_cfa_offset = 0;
int rem_ra_cfa_offset = 8;
int rem_bp_cfa_offset = INT_MAX;
while ((_buf < end) && (_current_pc < pc)) {
unsigned char op = *_buf++;
@ -147,7 +146,6 @@ void DwarfParser::parse_dwarf_instructions(uintptr_t begin, uintptr_t pc, const
enum DWARF_Register reg = static_cast<enum DWARF_Register>(opa);
if (reg == RBP) {
_bp_cfa_offset = operand1 * _data_factor;
_bp_offset_available = true;
} else if (reg == RA) {
_ra_cfa_offset = operand1 * _data_factor;
}
@ -184,6 +182,14 @@ void DwarfParser::parse_dwarf_instructions(uintptr_t begin, uintptr_t pc, const
}
break;
}
case 0x07: { // DW_CFA_undefined
enum DWARF_Register reg = static_cast<enum DWARF_Register>(read_leb(false));
// We are only interested in BP here because CFA and RA should not be undefined.
if (reg == RBP) {
_bp_cfa_offset = INT_MAX;
}
break;
}
case 0x0d: {// DW_CFA_def_cfa_register
_cfa_reg = static_cast<enum DWARF_Register>(read_leb(false));
break;

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, NTT DATA.
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, NTT DATA.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -73,7 +73,6 @@ class DwarfParser {
int _cfa_offset;
int _ra_cfa_offset;
int _bp_cfa_offset;
bool _bp_offset_available;
uintptr_t read_leb(bool sign);
uint64_t get_entry_length();
@ -86,15 +85,14 @@ class DwarfParser {
DwarfParser(lib_info *lib) : _lib(lib),
_buf(NULL),
_encoding(0),
_cfa_reg(RSP),
_cfa_reg(MAX_VALUE),
_return_address_reg(RA),
_code_factor(0),
_data_factor(0),
_current_pc(0L),
_cfa_offset(0),
_ra_cfa_offset(0),
_bp_cfa_offset(0),
_bp_offset_available(false) {};
_ra_cfa_offset(8),
_bp_cfa_offset(INT_MAX) {};
~DwarfParser() {}
bool process_dwarf(const uintptr_t pc);
@ -102,7 +100,6 @@ class DwarfParser {
int get_cfa_offset() { return _cfa_offset; }
int get_ra_cfa_offset() { return _ra_cfa_offset; }
int get_bp_cfa_offset() { return _bp_cfa_offset; }
bool is_bp_offset_available() { return _bp_offset_available; }
bool is_in(long pc) {
return (_lib->exec_start <= pc) && (pc < _lib->exec_end);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, Red Hat Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -81,11 +81,7 @@ class LinuxCDebugger implements CDebugger {
String cpu = dbg.getCPU();
if (cpu.equals("amd64")) {
AMD64ThreadContext context = (AMD64ThreadContext) thread.getContext();
Address sp = context.getRegisterAsAddress(AMD64ThreadContext.RSP);
if (sp == null) return null;
Address pc = context.getRegisterAsAddress(AMD64ThreadContext.RIP);
if (pc == null) return null;
return LinuxAMD64CFrame.getTopFrame(dbg, sp, pc, context);
return LinuxAMD64CFrame.getTopFrame(dbg, context);
} else if (cpu.equals("ppc64")) {
PPC64ThreadContext context = (PPC64ThreadContext) thread.getContext();
Address sp = context.getRegisterAsAddress(PPC64ThreadContext.SP);

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, NTT DATA.
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, NTT DATA.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -66,9 +66,15 @@ public class DwarfParser {
processDwarf0(pc.asLongValue());
}
/**
* @return true if BP offset is declared in DWARF instructions.
*/
public boolean isBPOffsetAvailable() {
return getBasePointerOffsetFromCFA() != Integer.MAX_VALUE;
}
public native int getCFARegister();
public native int getCFAOffset();
public native int getReturnAddressOffsetFromCFA();
public native int getBasePointerOffsetFromCFA();
public native boolean isBPOffsetAvailable();
}

View File

@ -40,8 +40,9 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
private static LinuxAMD64CFrame getFrameFromReg(LinuxDebugger dbg, Function<Integer, Address> getreg) {
Address rip = getreg.apply(AMD64ThreadContext.RIP);
Address rsp = getreg.apply(AMD64ThreadContext.RSP);
Address rbp = getreg.apply(AMD64ThreadContext.RBP);
Address libptr = dbg.findLibPtrByAddress(rip);
Address cfa = getreg.apply(AMD64ThreadContext.RBP);
Address cfa = null;
DwarfParser dwarf = null;
if (libptr != null) { // Native frame
@ -52,61 +53,34 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
// DWARF processing should succeed when the frame is native
// but it might fail if Common Information Entry (CIE) has language
// personality routine and/or Language Specific Data Area (LSDA).
return new LinuxAMD64CFrame(dbg, rsp, cfa, rip, dwarf, true);
return new LinuxAMD64CFrame(dbg, rsp, rbp, cfa, rip, dwarf, true);
}
cfa = getreg.apply(dwarf.getCFARegister())
.addOffsetTo(dwarf.getCFAOffset());
}
return (cfa == null) ? null
: new LinuxAMD64CFrame(dbg, rsp, cfa, rip, dwarf);
return (rbp == null && cfa == null)
? null
: new LinuxAMD64CFrame(dbg, rsp, rbp, cfa, rip, dwarf);
}
public static LinuxAMD64CFrame getTopFrame(LinuxDebugger dbg, Address rip, ThreadContext context) {
public static LinuxAMD64CFrame getTopFrame(LinuxDebugger dbg, ThreadContext context) {
return getFrameFromReg(dbg, context::getRegisterAsAddress);
}
public static LinuxAMD64CFrame getTopFrame(LinuxDebugger dbg, Address rsp, Address rip, ThreadContext context) {
Address libptr = dbg.findLibPtrByAddress(rip);
Address cfa = context.getRegisterAsAddress(AMD64ThreadContext.RBP);
DwarfParser dwarf = null;
if (libptr != null) { // Native frame
dwarf = new DwarfParser(libptr);
try {
dwarf.processDwarf(rip);
} catch (DebuggerException e) {
// DWARF processing should succeed when the frame is native
// but it might fail if Common Information Entry (CIE) has language
// personality routine and/or Language Specific Data Area (LSDA).
return new LinuxAMD64CFrame(dbg, rsp, cfa, rip, dwarf, true);
}
cfa = context.getRegisterAsAddress(dwarf.getCFARegister())
.addOffsetTo(dwarf.getCFAOffset());
}
return (cfa == null) ? null
: new LinuxAMD64CFrame(dbg, rsp, cfa, rip, dwarf);
private LinuxAMD64CFrame(LinuxDebugger dbg, Address rsp, Address rbp, Address cfa, Address rip, DwarfParser dwarf) {
this(dbg, rsp, rbp, cfa, rip, dwarf, false);
}
private LinuxAMD64CFrame(LinuxDebugger dbg, Address rsp, Address cfa, Address rip, DwarfParser dwarf) {
this(dbg, rsp, cfa, rip, dwarf, false);
}
private LinuxAMD64CFrame(LinuxDebugger dbg, Address rsp, Address cfa, Address rip, DwarfParser dwarf, boolean finalFrame) {
this(dbg, rsp, cfa, rip, dwarf, finalFrame, false);
}
private LinuxAMD64CFrame(LinuxDebugger dbg, Address rsp, Address cfa, Address rip, DwarfParser dwarf, boolean finalFrame, boolean use1ByteBeforeToLookup) {
private LinuxAMD64CFrame(LinuxDebugger dbg, Address rsp, Address rbp, Address cfa, Address rip, DwarfParser dwarf, boolean use1ByteBeforeToLookup) {
super(dbg.getCDebugger());
this.rsp = rsp;
this.rbp = rbp;
this.cfa = cfa;
this.rip = rip;
this.dbg = dbg;
this.dwarf = dwarf;
this.finalFrame = finalFrame;
this.use1ByteBeforeToLookup = use1ByteBeforeToLookup;
}
@ -126,14 +100,16 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
}
public Address localVariableBase() {
return cfa;
return (dwarf != null && dwarf.isBPOffsetAvailable())
? cfa.addOffsetTo(dwarf.getBasePointerOffsetFromCFA())
: rbp;
}
private Address getNextPC(boolean useDwarf) {
private Address getNextPC() {
try {
long offs = useDwarf ? dwarf.getReturnAddressOffsetFromCFA()
: ADDRESS_SIZE;
return cfa.getAddressAt(offs);
return dwarf == null
? rbp.getAddressAt(ADDRESS_SIZE) // Java frame
: cfa.getAddressAt(dwarf.getReturnAddressOffsetFromCFA()); // Native frame
} catch (UnmappedAddressException | UnalignedAddressException e) {
return null;
}
@ -144,41 +120,40 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
// nextCFA must be greater than current CFA, if frame is native.
// Java interpreter frames can share the CFA (frame pointer).
return nextCFA != null &&
(!isNative || (isNative && nextCFA.greaterThan(cfa)));
(!isNative || (isNative && nextCFA.greaterThanOrEqual(cfa)));
}
private Address getNextRSP() {
// next RSP should be previous slot of return address.
var bp = dwarf == null ? cfa.addOffsetTo(ADDRESS_SIZE) // top of BP points callser BP
: cfa.addOffsetTo(dwarf.getReturnAddressOffsetFromCFA());
return bp.addOffsetTo(ADDRESS_SIZE);
return dwarf == null ? rbp.addOffsetTo(2 * ADDRESS_SIZE) // Java frame - skip saved BP and RA
: cfa.addOffsetTo(dwarf.getReturnAddressOffsetFromCFA())
.addOffsetTo(ADDRESS_SIZE); // Native frame
}
private Address getNextCFA(DwarfParser nextDwarf, ThreadContext context, Address senderFP, Address senderPC) {
private Address getNextCFA(DwarfParser nextDwarf, Address senderFP, Address senderPC) {
Address nextCFA;
boolean isNative = false;
if (senderFP == null) {
senderFP = cfa.getAddressAt(0); // RBP by default
}
if (VM.getVM().getCodeCache().contains(senderPC)) { // Next frame is Java
nextCFA = (dwarf == null) ? senderFP // Current frame is Java
: cfa.getAddressAt(dwarf.getBasePointerOffsetFromCFA()); // Current frame is Native
if (nextDwarf == null) { // Next frame is Java
return null;
} else { // Next frame is Native
if (VM.getVM().getCodeCache().contains(pc())) { // Current frame is Java
nextCFA = senderFP.addOffsetTo(-nextDwarf.getBasePointerOffsetFromCFA());
} else { // Current frame is Native
if (nextDwarf == null) { // maybe runtime entrypoint (_start())
throw new DebuggerException("nextDwarf is null even though native call");
if (dwarf == null) { // Current frame is Java
int nextCFAReg = nextDwarf.getCFARegister();
if (nextCFAReg == AMD64ThreadContext.RBP) {
nextCFA = nextDwarf.isBPOffsetAvailable()
? rbp.getAddressAt(0) // We can use cfa as BP in Java frame
.addOffsetTo(-nextDwarf.getBasePointerOffsetFromCFA())
: rbp;
} else if (nextCFAReg == AMD64ThreadContext.RSP) {
nextCFA = senderFP.addOffsetTo(2 * ADDRESS_SIZE) // skip BP and RA to get caller SP
.addOffsetTo(nextDwarf.getCFAOffset());
} else {
throw new DebuggerException("Unsupported CFA register: " + nextCFAReg);
}
} else { // Current frame is Native
isNative = true;
int nextCFAReg = nextDwarf.getCFARegister();
if (nextCFAReg == AMD64ThreadContext.RBP) {
Address rbp = dwarf.isBPOffsetAvailable() ? cfa.addOffsetTo(dwarf.getBasePointerOffsetFromCFA())
: context.getRegisterAsAddress(AMD64ThreadContext.RBP);
Address nextRBP = rbp.getAddressAt(0);
Address nextRBP = getNextRBP(senderFP);
nextCFA = nextRBP.addOffsetTo(-nextDwarf.getBasePointerOffsetFromCFA());
} else if (nextCFAReg == AMD64ThreadContext.RSP) {
nextCFA = getNextRSP().addOffsetTo(nextDwarf.getCFAOffset());
@ -188,14 +163,6 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
}
}
// Sanity check for next CFA address
try {
nextCFA.getAddressAt(0);
} catch (Exception e) {
// return null if next CFA address is invalid
return null;
}
if (dbg.isSignalTrampoline(senderPC)) {
// Return without frame check if sender is signal trampoline.
return nextCFA;
@ -204,6 +171,18 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
}
}
private Address getNextRBP(Address senderFP) {
if (senderFP != null) {
return senderFP;
} else if (dwarf == null) { // Current frame is Java
return rbp.getAddressAt(0);
} else { // Current frame is Native
return dwarf.isBPOffsetAvailable()
? cfa.getAddressAt(dwarf.getBasePointerOffsetFromCFA())
: rbp;
}
}
@Override
public CFrame sender(ThreadProxy th) {
return sender(th, null, null, null);
@ -211,10 +190,6 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
@Override
public CFrame sender(ThreadProxy th, Address sp, Address fp, Address pc) {
if (finalFrame) {
return null;
}
if (dbg.isSignalTrampoline(pc())) {
// RSP points signal context
// https://github.com/torvalds/linux/blob/v6.17/arch/x86/kernel/signal.c#L94
@ -227,8 +202,7 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
if (nextRSP == null) {
return null;
}
Address nextPC = pc != null ? pc : getNextPC(dwarf != null);
Address nextPC = pc != null ? pc : getNextPC();
if (nextPC == null) {
return null;
}
@ -251,11 +225,16 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
}
}
Address nextRBP = getNextRBP(fp);
try {
Address nextCFA = getNextCFA(nextDwarf, context, fp, nextPC);
return new LinuxAMD64CFrame(dbg, nextRSP, nextCFA, nextPC, nextDwarf, false, fallback);
Address nextCFA = getNextCFA(nextDwarf, fp, nextPC);
return (nextCFA == null && nextRBP == null)
? null
: new LinuxAMD64CFrame(dbg, nextRSP, nextRBP, nextCFA, nextPC, nextDwarf, fallback);
} catch (DebuggerException _) {
return null;
return nextRBP == null ? null
: new LinuxAMD64CFrame(dbg, nextRSP, nextRBP, null, nextPC, null, fallback);
}
}
@ -279,16 +258,16 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
@Override
public Frame toFrame() {
return new AMD64Frame(rsp, cfa, rip);
return new AMD64Frame(rsp, localVariableBase(), rip);
}
// package/class internals only
private static final int ADDRESS_SIZE = 8;
private Address rsp;
private Address rbp;
private Address rip;
private Address cfa;
private LinuxDebugger dbg;
private DwarfParser dwarf;
private boolean finalFrame;
private boolean use1ByteBeforeToLookup;
}

View File

@ -32,7 +32,7 @@ import jdk.test.lib.util.CoreUtils;
/**
* @test
* @bug 8374482
* @bug 8374482 8376264
* @requires (os.family == "linux") & (vm.hasSA)
* @requires os.arch == "amd64"
* @library /test/lib
@ -61,6 +61,7 @@ public class TestJhsdbJstackMixedCore {
out.shouldContain("<signal handler called>");
out.shouldContain("Java_jdk_test_lib_apps_LingeredApp_crash");
out.shouldContain("* jdk.test.lib.apps.LingeredApp.crash()");
}
public static void main(String... args) throws Throwable {