Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/stackFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#ifndef _STACKFRAME_H
#define _STACKFRAME_H

#include <stddef.h>
#include <stdint.h>
#include <ucontext.h>
#include "arch.h"
Expand Down Expand Up @@ -68,6 +69,52 @@ class StackFrame {
bool unwindCompiled(NMethod* nm, uintptr_t& pc, uintptr_t& sp, uintptr_t& fp);
bool unwindAtomicStub(const void*& pc);

/* ===========================================================================
* ONE-STEP **LEAF-FRAME** UNWIND — no prologue, no frame-pointer
*
* Applies only when we interrupt inside a *true* leaf (PLT veneer, 1-inst
* syscall stub, musl clone entry, etc.). The callee has **not** saved
* FP/LR to memory, so the *only* recoverable information is the caller’s
* return address that the hardware or ABI guarantees to exist.
*
* ┌─ prerequisites ─────────────────────────────────────────────────────────
* │ • sp still points to the caller’s stack frame
* │ • regs = live register set from ucontext (if the ABI keeps LR in reg)
* │ • sanity check **AFTER** each step:
* │ – pc inside executable – sp stays inside thread stack
* │ – sp keeps natural alignment (4- or 8-byte)
* └─────────────────────────────────────────────────────────────────────────
*
* x86-32 SysV
* pc = ((void**)sp)[0]; // return RIP pushed by CALL
* sp += 4;
* fp = *(uint32_t*)sp; // may be 0
*
* x86-64 SysV
* pc = ((void**)sp)[0]; // return RIP pushed by CALL
* sp += 8;
* fp = *(uint64_t*)sp; // may be 0
*
* AArch64 (AAPCS64, musl & glibc)
* pc = regs->regs[30]; // LR (x30) – ONLY place it lives
* // sp & fp (x29) unchanged; fp may be 0
*
* armv7 (EABI)
* pc = regs->arm_lr; // r14
* // sp & fp (r11) unchanged
*
* RISC-V RV64 (LoongArch64 identical)
* pc = regs->ra; // x1
* // sp unchanged; fp (s0) unchanged
*
* PPC64 ELFv2
* // Leaf functions never build the 32-byte save area and never push LR
* // → LR exists only in r0 while we execute. A memory-only unwinder
* // cannot advance → record one native leaf (“ppc64_leaf”) and stop.
*
* ========================================================================= */
bool unwindFramelessLeaf(const void*& pc, uintptr_t& sp, uintptr_t& fp);

void adjustSP(const void* entry, const void* pc, uintptr_t& sp);

bool skipFaultInstruction();
Expand Down
6 changes: 6 additions & 0 deletions src/stackFrame_aarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <errno.h>
#include <string.h>
#include <sys/syscall.h>
#include "arch.h"
#include "stackFrame.h"
#include "safeAccess.h"
#include "vmStructs.h"
Expand Down Expand Up @@ -171,6 +172,11 @@ bool StackFrame::unwindAtomicStub(const void*& pc) {
return false;
}

bool StackFrame::unwindFramelessLeaf(const void*& pc, uintptr_t& sp, uintptr_t& fp) {
pc = (const void*)link();
return true;
}

void StackFrame::adjustSP(const void* entry, const void* pc, uintptr_t& sp) {
instruction_t* ip = (instruction_t*)pc;
if (ip > entry && (ip[-1] == 0xa9bf27ff || (ip[-1] == 0xd63f0100 && ip[-2] == 0xa9bf27ff))) {
Expand Down
5 changes: 5 additions & 0 deletions src/stackFrame_arm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,9 @@ bool StackFrame::isSyscall(instruction_t* pc) {
return *pc == 0xef000000;
}

bool StackFrame::unwindFramelessLeaf(const void*& pc, uintptr_t& sp, uintptr_t& fp) {
pc = (const void*)link();
return true;
}

#endif // defined(__arm__) || defined(__thumb__)
7 changes: 7 additions & 0 deletions src/stackFrame_i386.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,11 @@ bool StackFrame::isSyscall(instruction_t* pc) {
return pc[0] == 0xcd && pc[1] == 0x80;
}

bool StackFrame::unwindFramelessLeaf(const void*& pc, uintptr_t& sp, uintptr_t& fp) {
pc = *(const void**)this->sp();
sp = this->sp() + 4;
fp = *(uintptr_t*)this->fp();
return true;
}

#endif // __i386__
5 changes: 5 additions & 0 deletions src/stackFrame_loongarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,9 @@ bool StackFrame::isSyscall(instruction_t* pc) {
return (*pc) == 0x002b0000;
}

bool StackFrame::unwindFramelessLeaf(const void*& pc, uintptr_t& sp, uintptr_t& fp) {
pc = (const void*)link();
return true;
}

#endif // __loongarch_lp64
4 changes: 4 additions & 0 deletions src/stackFrame_ppc64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,8 @@ bool StackFrame::isSyscall(instruction_t* pc) {
return (*pc & 0x1f) == 17;
}

bool StackFrame::unwindFramelessLeaf(const void*& pc, uintptr_t& sp, uintptr_t& fp) {
return false;
}

#endif // defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
5 changes: 5 additions & 0 deletions src/stackFrame_riscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,9 @@ bool StackFrame::isSyscall(instruction_t* pc) {
return (*pc) == 0x00000073;
}

bool StackFrame::unwindFrameless(const void*& pc, uintptr_t& sp, uintptr_t& fp, uintptr_t bottom) {
pc = (const void*)link();
return false;
}

#endif // riscv
7 changes: 7 additions & 0 deletions src/stackFrame_x64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,11 @@ bool StackFrame::isSyscall(instruction_t* pc) {
return pc[0] == 0x0f && pc[1] == 0x05;
}

bool StackFrame::unwindFramelessLeaf(const void*& pc, uintptr_t& sp, uintptr_t& fp) {
pc = *(const void**)this->sp();
sp = this->sp() + 8;
fp = *(uintptr_t*)this->fp();
return true;
}

#endif // __x86_64__
86 changes: 81 additions & 5 deletions src/stackWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "profiler.h"
#include "safeAccess.h"
#include "stackFrame.h"
#include "symbols.h"
#include "vmStructs.h"


Expand All @@ -18,7 +19,6 @@ const intptr_t MAX_FRAME_SIZE = 0x40000;
const intptr_t MAX_INTERPRETER_FRAME_SIZE = 0x1000;
const intptr_t DEAD_ZONE = 0x1000;


static inline bool aligned(uintptr_t ptr) {
return (ptr & (sizeof(uintptr_t) - 1)) == 0;
}
Expand Down Expand Up @@ -228,6 +228,23 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
return walkVM(ucontext, frames, max_depth, VM_BASIC, pc, sp, fp);
}

static int tryRecoverFromAnchor(JavaFrameAnchor** anchor, bool* in_java,
uintptr_t& sp, uintptr_t& fp, const void*& pc) {
if (*anchor != NULL && !*in_java && (*anchor)->lastJavaFP() > 0) {
if (!VMStructs::goodPtr((void*)((*anchor)->lastJavaSP())) && !VMStructs::goodPtr((void*)(*anchor)->lastJavaFP())) {
// end of Java stack; break stack walking
return -1;
}
fp = VMStructs::goodPtr((const void*)(*anchor)->lastJavaFP()) ? (*anchor)->lastJavaFP() : 0;
sp = VMStructs::goodPtr((const void*)(*anchor)->lastJavaSP()) ? (*anchor)->lastJavaSP() : 0;
pc = (*anchor)->lastJavaPC();

*in_java = true;
return 1;
}
return 0;
}

int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
StackDetail detail, const void* pc, uintptr_t sp, uintptr_t fp) {
StackFrame frame(ucontext);
Expand All @@ -243,6 +260,9 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
// Should be preserved across setjmp/longjmp
volatile int depth = 0;

JavaFrameAnchor* anchor = NULL;
bool in_java = false;

if (vm_thread != NULL) {
vm_thread->exception() = &crash_protection_ctx;
if (setjmp(crash_protection_ctx) != 0) {
Expand All @@ -252,11 +272,13 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
}
return depth;
}
anchor = vm_thread->anchor();
}

// Walk until the bottom of the stack or until the first Java frame
while (depth < max_depth) {
if (CodeHeap::contains(pc)) {
in_java = true;
NMethod* nm = CodeHeap::findNMethod(pc);
if (nm == NULL) {
fillFrame(frames[depth++], BCI_ERROR, "unknown_nmethod");
Expand All @@ -265,8 +287,9 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
FrameTypeId type = detail != VM_BASIC && level >= 1 && level <= 3 ? FRAME_C1_COMPILED : FRAME_JIT_COMPILED;
fillFrame(frames[depth++], type, 0, nm->method()->id());

bool should_break = true;
int scope_offset = nm->findScopeOffset(pc);
if (nm->isFrameCompleteAt(pc)) {
int scope_offset = nm->findScopeOffset(pc);
if (scope_offset > 0) {
depth--;
ScopeDesc scope(nm);
Expand All @@ -289,9 +312,13 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
continue;
} else if (frame.unwindCompiled(nm, (uintptr_t&)pc, sp, fp) && profiler->isAddressInCode(pc)) {
continue;
} else if (nm->isIntrinsic() && scope_offset < 0) {
// let's make this fall-through and try applying the default unwinding on native-looking method
should_break = false;
}
if (should_break) {
fillFrame(frames[depth++], BCI_ERROR, "break_compiled");
}

fillFrame(frames[depth++], BCI_ERROR, "break_compiled");
break;
} else if (nm->isInterpreter()) {
if (vm_thread != NULL && vm_thread->inDeopt()) {
Expand Down Expand Up @@ -379,7 +406,48 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
}
}
} else {
fillFrame(frames[depth++], BCI_NATIVE_FRAME, profiler->findNativeMethod(pc));
const char* method = profiler->findNativeMethod(pc);
if (method == NULL) {
if (!method) {
// this would produce [unknown] frames; let's try harder to recover
if (!profiler->isAddressInCode(pc)) {
// bogus PC; let's try recovering from java frame anchor
int res = tryRecoverFromAnchor(&anchor, &in_java, sp, fp, pc);
if (res == 0) {
// record skipped frames, replace the bogus PC with the leaf Java frame and continue
fillFrame(frames[depth++], BCI_ERROR, "skipped frames");
continue;
} else if (res == -1) {
// end of Java stack; break stack walking
break;
} else if (res == 1) {
// unable to recover from anchor; we are completely lost
fillFrame(frames[depth++], BCI_ERROR, "break_walk");
break;
}
}
}
}
// musl is 'special' - the PLT veneers have debug info but if we land before the SP is pushed, it will take us to weird places
if (OS::isMusl() && Symbols::isInMuslLoader(pc)) {
if (depth == 1) {
const void* pc_back = pc;
uintptr_t sp_back = sp;
uintptr_t fp_back = fp;

if (frame.unwindFramelessLeaf(pc, sp, fp)) {
if (profiler->isAddressInCode(pc)) {
// the unwind looks plausible, let's use it
continue;
}
// something went wrong, restore the registers and try the default path
pc = pc_back;
sp = sp_back;
fp = fp_back;
}
}
}
fillFrame(frames[depth++], BCI_NATIVE_FRAME, method);
}

uintptr_t prev_sp = sp;
Expand All @@ -389,8 +457,16 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
u8 cfa_reg = (u8)f->cfa;
int cfa_off = f->cfa >> 8;
if (cfa_reg == DW_REG_SP) {
if (sp >= bottom || !VMStructs::goodPtr((const void*)sp)) {
// sanity check; we don't want to keep unwinding based on bogus values
break;
}
sp = sp + cfa_off;
} else if (cfa_reg == DW_REG_FP) {
if (!VMStructs::goodPtr((const void*)fp)) {
// sanity check; we don't want to keep unwinding based on bogus values
break;
}
sp = fp + cfa_off;
} else if (cfa_reg == DW_REG_PLT) {
sp += ((uintptr_t)pc & 15) >= 11 ? cfa_off * 2 : cfa_off;
Expand Down
2 changes: 2 additions & 0 deletions src/symbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Symbols {
static bool haveKernelSymbols() {
return _have_kernel_symbols;
}

static bool isInMuslLoader(const void* pc);
};

class UnloadProtection {
Expand Down
26 changes: 26 additions & 0 deletions src/symbols_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ static const void* getMainPhdr() {
static const void* _main_phdr = getMainPhdr();
static const char* _ld_base = (const char*)getauxval(AT_BASE);

struct { uintptr_t lo, hi; } _musl = {0, 0};

static void cache_musl_loader_range() {
dl_iterate_phdr([](struct dl_phdr_info* info, size_t, void*) {
if (info->dlpi_name && strstr(info->dlpi_name, "ld-musl") != nullptr) {
for (int i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr)& ph = info->dlpi_phdr[i];
if (ph.p_type == PT_LOAD && (ph.p_flags & PF_X)) {
uintptr_t base = info->dlpi_addr + ph.p_vaddr;
_musl.lo = base;
_musl.hi = base + ph.p_memsz;
return 1; // stop iteration
}
}
}
return 0;
}, nullptr);
}

/* call once during agent start-up */
static int _dummy = (cache_musl_loader_range(), 0);

static bool isMainExecutable(const char* image_base, const void* map_end) {
return _main_phdr != NULL && _main_phdr >= image_base && _main_phdr < map_end;
}
Expand Down Expand Up @@ -777,6 +799,10 @@ static void collectSharedLibraries(std::unordered_map<u64, SharedLibrary>& libs,
fclose(f);
}

bool Symbols::isInMuslLoader(const void* address) {
return (uintptr_t)address >= _musl.lo && (uintptr_t)address < _musl.hi;
}

void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
MutexLocker ml(_parse_lock);

Expand Down
Loading
Loading