From 9a8c7ec857096a47c7c97a96b1e3e3a43076d328 Mon Sep 17 00:00:00 2001 From: Yasumasa Suenaga Date: Sun, 25 Jan 2026 00:07:55 +0900 Subject: [PATCH] 8376264: Mixed jstack could not unwind optimized frame --- .../linux/native/libsaproc/DwarfParser.cpp | 17 +- .../linux/native/libsaproc/dwarf.cpp | 24 +-- .../linux/native/libsaproc/dwarf.hpp | 13 +- .../debugger/linux/LinuxCDebugger.java | 8 +- .../debugger/linux/amd64/DwarfParser.java | 12 +- .../linux/amd64/LinuxAMD64CFrame.java | 145 ++++++++---------- .../sa/TestJhsdbJstackMixedCore.java | 3 +- 7 files changed, 97 insertions(+), 125 deletions(-) diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/DwarfParser.cpp b/src/jdk.hotspot.agent/linux/native/libsaproc/DwarfParser.cpp index 8df08c49e09..6c94992e1e2 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/DwarfParser.cpp +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/DwarfParser.cpp @@ -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(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(get_dwarf_context(env, this_obj)); - return parser->is_bp_offset_available(); -} - diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp b/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp index a0c54230530..2636bdf691a 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp @@ -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(-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(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(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(read_leb(false)); break; diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp b/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp index a047ffae247..a2692738ce1 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp @@ -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); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxCDebugger.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxCDebugger.java index e3543503216..15f6615421c 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxCDebugger.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxCDebugger.java @@ -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); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/DwarfParser.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/DwarfParser.java index 3b63ee0a21e..53351c918d3 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/DwarfParser.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/DwarfParser.java @@ -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(); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java index 4bf6a0305a3..70cd1fa72ce 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java @@ -40,8 +40,9 @@ public final class LinuxAMD64CFrame extends BasicCFrame { private static LinuxAMD64CFrame getFrameFromReg(LinuxDebugger dbg, Function 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; } diff --git a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedCore.java b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedCore.java index b8b19c743e9..28de84d3109 100644 --- a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedCore.java +++ b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedCore.java @@ -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(""); 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 {