Skip to content

Instantly share code, notes, and snippets.

@MaskRay
Created May 19, 2023 03:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MaskRay/3d05f5613a2f8e774aad26ee3da4e696 to your computer and use it in GitHub Desktop.
Save MaskRay/3d05f5613a2f8e774aad26ee3da4e696 to your computer and use it in GitHub Desktop.
Stack unwinding in glibc

glibc functions perform stack unwinding mainly in two places, pthread_exit/pthread_cancel, and backtrace-family functions.

pthread_exit/pthread_cancel.

Here is a C++ program that calls pthread_exit. Shall the destructors ~A and ~B be called?

#include <pthread.h>
#include <stdio.h>

void foo() { pthread_exit(NULL); }

void bar() {
  struct A { ~A() { puts("~A"); } } a;
  foo();
}

void *qux(void *arg) {
  struct B { ~B() { puts("~B"); } } b;
  bar();
  return nullptr;
}

int main() {
  pthread_t t;
  pthread_create(&t, nullptr, qux, nullptr);
  pthread_join(t, nullptr);
}

POSIX doesn't have the requirement, but glibc and FreeBSD made a decision to call destructors.

In glibc, pthread_exit calls dlopen("libgcc_s.so.1", RTLD_NOW | __RTLD_DLOPEN) and then uses dlsym to retrieve definitions like _Unwind_ForcedUnwind and _Unwind_GetIP. If libgcc_s.so.1 is unavailable, glibc exits with an error message "libgcc_s.so.1 must be installed for pthread_exit to work".

If libgcc_s.so.1 is available, __pthread_unwind will invoke _Unwind_ForcedUnwind to call destructors and then invoke __libc_unwind_longjmp to go back to the saved point in pthread_create.

backtrace

Second, functions backtrace, backtrace_symbols, and backtrace_symbols_fd declared in <execinfo.h> perform stack unwinding.

When an executable is linked against libunwind.so or libunwind.a from llvm-project, there is an alternative implementation of _Unwind_* functions.

If libgcc_s.so.1:_Unwind_Backtrace calls external _Unwind_* helper functions, these calls will be resolved to libunwind.so. However, this cross-walking can cause issues if the _Unwind_Context created by libgcc is accessed by libunwind, as the two have different layouts of _Unwind_Context.

ChromeOS has an issue related to this problem in glibc on AArch32. libgcc's _Unwind_Backtrace calls _Unwind_SetGR (inline function in gcc/ginclude/unwind-arm-common.h), which calls _Unwind_VRS_Set in libunwind.

Gentoo has compiled a list of packages not building with musl due to the absence of backtrace. backtrace is a scope creep for the C library and needs the .eh_frame unwind table, so I don't recommend it.

An alternative option is to use libbacktrace. Otherwise, you can simply utilize _Unwind_Backtrace provided by either libunwind or libgcc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment