/*
 * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

package sun.jvm.hotspot.debugger.linux.amd64;

import java.util.function.Function;

import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.debugger.amd64.*;
import sun.jvm.hotspot.debugger.linux.*;
import sun.jvm.hotspot.debugger.linux.amd64.*;
import sun.jvm.hotspot.debugger.cdbg.*;
import sun.jvm.hotspot.debugger.cdbg.basic.*;
import sun.jvm.hotspot.runtime.*;
import sun.jvm.hotspot.runtime.amd64.*;

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 libptr = dbg.findLibPtrByAddress(rip);
      Address cfa = getreg.apply(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 = getreg.apply(dwarf.getCFARegister())
                    .addOffsetTo(dwarf.getCFAOffset());
      }

      return (cfa == null) ? null
                           : new LinuxAMD64CFrame(dbg, rsp, cfa, rip, dwarf);
   }

   public static LinuxAMD64CFrame getTopFrame(LinuxDebugger dbg, Address rip, 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 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) {
      super(dbg.getCDebugger());
      this.rsp = rsp;
      this.cfa = cfa;
      this.rip = rip;
      this.dbg = dbg;
      this.dwarf = dwarf;
      this.finalFrame = finalFrame;
      this.use1ByteBeforeToLookup = use1ByteBeforeToLookup;
   }

   // override base class impl to avoid ELF parsing
   public ClosestSymbol closestSymbolToPC() {
      Address symAddr = use1ByteBeforeToLookup ? pc().addOffsetTo(-1) : pc();

      // Returns a special symbol if the address is signal handler,
      // otherwise returns closest symbol generated by LinuxDebugger.
      return dbg.isSignalTrampoline(symAddr)
        ? new ClosestSymbol("<signal handler called>", 0)
        : dbg.lookup(dbg.getAddressValue(symAddr));
   }

   public Address pc() {
      return rip;
   }

   public Address localVariableBase() {
      return cfa;
   }

   private Address getNextPC(boolean useDwarf) {
     try {
       long offs = useDwarf ? dwarf.getReturnAddressOffsetFromCFA()
                            : ADDRESS_SIZE;
       return cfa.getAddressAt(offs);
     } catch (UnmappedAddressException | UnalignedAddressException e) {
       return null;
     }
   }

   private boolean isValidFrame(Address nextCFA, boolean isNative) {
     // CFA should never be null.
     // 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)));
   }

   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);
   }

   private Address getNextCFA(DwarfParser nextDwarf, ThreadContext context, 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
     } 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");
         }

         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);
           nextCFA = nextRBP.addOffsetTo(-nextDwarf.getBasePointerOffsetFromCFA());
         } else if (nextCFAReg == AMD64ThreadContext.RSP) {
           nextCFA = getNextRSP().addOffsetTo(nextDwarf.getCFAOffset());
         } else {
           throw new DebuggerException("Unsupported CFA register: " + nextCFAReg);
         }
       }
     }

     // 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;
     } else {
       return isValidFrame(nextCFA, isNative) ? nextCFA : null;
     }
   }

   @Override
   public CFrame sender(ThreadProxy th) {
     return sender(th, null, null, null);
   }

   @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
       return getFrameFromReg(dbg, r -> LinuxAMD64ThreadContext.getRegFromSignalTrampoline(this.rsp, r.intValue()));
     }

     ThreadContext context = th.getContext();

     Address nextRSP = sp != null ? sp : getNextRSP();
     if (nextRSP == null) {
       return null;
     }

     Address nextPC = pc != null ? pc : getNextPC(dwarf != null);
     if (nextPC == null) {
       return null;
     }

     DwarfParser nextDwarf = null;
     boolean fallback = false;
     try {
       nextDwarf = createDwarfParser(nextPC);
     } catch (DebuggerException _) {
       // Try again with RIP-1 in case RIP is just outside function bounds,
       // due to function ending with a `call` instruction.
       try {
         nextDwarf = createDwarfParser(nextPC.addOffsetTo(-1));
         fallback = true;
       } catch (DebuggerException _) {
         // 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 null;
       }
     }

     try {
       Address nextCFA = getNextCFA(nextDwarf, context, fp, nextPC);
       return new LinuxAMD64CFrame(dbg, nextRSP, nextCFA, nextPC, nextDwarf, false, fallback);
     } catch (DebuggerException _) {
       return null;
     }
   }

   private DwarfParser createDwarfParser(Address pc) throws DebuggerException {
     DwarfParser nextDwarf = null;
     Address libptr = dbg.findLibPtrByAddress(pc);
     if (libptr != null) {
       try {
         nextDwarf = new DwarfParser(libptr);
       } catch (DebuggerException _) {
         // Bail out to Java frame
       }
     }

     if (nextDwarf != null) {
       nextDwarf.processDwarf(pc);
     }

     return nextDwarf;
   }

   @Override
   public Frame toFrame() {
     return new AMD64Frame(rsp, cfa, rip);
   }

   // package/class internals only
   private static final int ADDRESS_SIZE = 8;
   private Address rsp;
   private Address rip;
   private Address cfa;
   private LinuxDebugger dbg;
   private DwarfParser dwarf;
   private boolean finalFrame;
   private boolean use1ByteBeforeToLookup;
}
