Skip to content
Merged
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ Check your package manager on how to install these dependencies (e.g.,
`apt-get install libdw-dev libelf-dev` in Debian-based systems). Note that you may need to tell the
compiler where to find the header and library files of the dependencies for the build to
succeed. Check your distribution's documentation to determine the location of the header and
library files or for more detailed information.
library files or for more detailed information. When building on Alpine Linux (or any other
distribution that doesn't use glibc) you'll need elfutils 0.188 or newer. You may need to build this
from source if your distribution's package manager doesn't have it.

Once you have these binary dependencies installed, you can clone the repository and follow the
typical build process for Python libraries:
Expand Down
28 changes: 23 additions & 5 deletions src/pystack/_pystack/unwinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,37 @@ frameCallback(Dwfl_Frame* state, void* arg)
return -1;
}

std::optional<Dwarf_Word> stackPointer;
// Unwinding through musl libc with elfutils can get stuck returning the
// same PC in a loop forever.
//
// https://sourceware.org/bugzilla/show_bug.cgi?id=30272
// https://marc.info/?l=musl&m=168053842303968&w=2
//
// We can work around this by asking elfutils what the stack pointer is for
// each frame and breaking out on our own if two different frames report
// the same stack pointer. As an optimization (and to avoid a hard
// dependency on a very recent version of elfutils), only do this check
// when PyStack isn't built against glibc. This isn't entirely correct, as
// it means that a PyStack built against glibc can fail to collect stacks
// for Python interpreters built against musl libc, but it's unlikely that
// users will encounter that. If they do the simple work around is to
// run PyStack using the same interpreter they want to get stacks for.

#if _ELFUTILS_VERSION >= 188 or (defined(__linux__) && !defined(__GLIBC__))

// These platform specific magic numbers are part of the platform ABI.
// For any platform not handled below we never look up the value of the
// stack pointer register, and so never return DWARF_CB_ABORT.
std::optional<Dwarf_Word> stackPointer;
std::optional<unsigned int> stackPointerRegNo;

#if defined(__linux__) && defined(__x86_64__)
# if defined(__x86_64__)
// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
// Figure 3.36: DWARF Register Number Mapping
stackPointerRegNo = 7;
#elif defined(__linux__) && defined(__aarch64__)
# elif defined(__aarch64__)
// https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html#DW-REG
stackPointerRegNo = 31;
#endif
# endif

if (stackPointerRegNo) {
stackPointer.emplace(0);
Expand All @@ -113,6 +130,7 @@ frameCallback(Dwfl_Frame* state, void* arg)
LOG(DEBUG) << std::hex << std::showbase << "Breaking out of (infinite?) unwind loop @ " << pc;
return DWARF_CB_ABORT;
}
#endif

frames->emplace_back(pc, isActivation, stackPointer);
return DWARF_CB_OK;
Expand Down