LIBUNWIND-PTRACE(3libunwind)

πŸ“¦
version 1.8.3 Β· 2 versions
, in alpine 3.22, alpine 3.23, alpine 3.24, arch rolling, epel 10, epel 9, fedora 43, fedora 44, nixpkgs unstable, opensuse-leap 16.0

✨ LIBUNWIND-PTRACE ✨

okay so bestie, welcome to the libunwind-ptrace page from the Programming Library manual (that's section 3libunwind if you're keeping score πŸ’…). this is the one that lets libunwind peek into another process. it's giving detective energy fr fr πŸ•΅οΈβ™€οΈ


🌸 NAME 🌸

libunwind-ptrace β€” ptrace() support in libunwind


πŸŽ€ SYNOPSIS πŸŽ€

#include <libunwind-ptrace.h>

unw_accessors_t _UPT_accessors;

void *_UPT_create(pid_t);
void _UPT_destroy(void *);

int _UPT_find_proc_info(unw_addr_space_t, unw_word_t, unw_proc_info_t *,
int, void *);
void _UPT_put_unwind_info(unw_addr_space_t, unw_proc_info_t *, void *);
int _UPT_get_dyn_info_list_addr(unw_addr_space_t, unw_word_t *, void *);
int _UPT_access_mem(unw_addr_space_t, unw_word_t, unw_word_t *, int,
void *);
int _UPT_access_reg(unw_addr_space_t, unw_regnum_t, unw_word_t *, int,
void *);
int _UPT_access_fpreg(unw_addr_space_t, unw_regnum_t, unw_fpreg_t *,
int, void *);
int _UPT_get_proc_name(unw_addr_space_t, unw_word_t, char *, size_t,
unw_word_t *, void *);
int _UPT_resume(unw_addr_space_t, unw_cursor_t *, void *);

πŸ’– DESCRIPTION πŸ’–

so like, the ptrace(2) system call is the iconic little syscall that lets one process get access to the machine state and the virtual memory of a whole other process. and the way that works, if you hand it the right set of callback routines, you can hook libunwind straight up to another process through ptrace(2). doing that by hand isn't exactly hard, but libunwind is out here being a whole bestie about it and ships ready-to-use callbacks so you don't have to. everything in this facility wears the name prefix _UPT, and that stands for ``unwind via ptrace''. no cap ✨

so if your app wants to use the libunwind ptrace remote, here's the little ritual you gotta do, step by step:

  1. make a brand new libunwind address space that represents the target process. you do that by calling unw_create_addr_space(). most of the time you'll just wanna pass the address of _UPT_accessors as the first argument to that routine, and doing so is what guarantees libunwind will be able to properly unwind the target process. periodt.
  2. turn on ptrace mode on the target process. two ways: either fork a new process, invoke PTRACE_TRACEME, and then start the target program (via execve(2)) β€” or just directly attach to a process that's already running (via PTRACE_ATTACH). pick your fighter πŸ₯Š
  3. once you know the process-ID (pid) of the target process, you can make a UPT info structure by calling _UPT_create(), passing the pid of the target process as the only argument.
  4. the opaque pointer it hands back? that needs to go in as the ``argument'' pointer β€” that's the third argument β€” to unw_init_remote().

okay and sometimes, in special circumstances, your app might only want portions of the libunwind ptrace remote, and that's valid. that's exactly why the individual callback routines (_UPT_find_proc_info(), _UPT_put_unwind_info(), etc.) are also available for direct use. like yeah, you could just pick the addresses of these routines up out of _UPT_accessors, but doing it that way would prevent static initialization. also lowkey important: when you use _UPT_accessors, all the callback routines get linked into your application, even the ones that are never actually called. dead weight but make it ✨intentional✨

the _UPT_resume() routine is your girl for resuming execution of the target process. she literally just invokes ptrace(2) with a command value of PTRACE_CONT. simple, clean, slay.

and when your application is done using libunwind on the target process, you have to call _UPT_destroy(), passing it the opaque pointer that came back from your call to _UPT_create(). this is the part that ensures all the memory and other resources actually get freed up. don't skip it bestie, we do not leak in this house πŸ™ˆ


πŸ¦‹ AVAILABILITY πŸ¦‹

because ptrace(2) only works within a single machine, the libunwind ptrace remote is by definition not available in libunwind versions that are configured for cross-unwinding. it's a one-machine kind of love πŸ’”


🌈 THREAD SAFETY 🌈

the libunwind ptrace remote assumes that a single UPT info structure is never shared between threads. because of that assumption, there's no explicit locking anywhere. so as long as only one thread is using a UPT info structure at any given time, this facility is thread-safe. share the UPT struct and all bets are off β€” that's the vibe check, iykyk 🫢


⭐ RETURN VALUE ⭐

_UPT_create() may return a NULL pointer if it fails to create the UPT info structure for any reason. in the current implementation, the only reason this call can fail is when the system is out of memory. so if you get NULL, check your RAM situation fr fr 😭


πŸ‘‘ FILES πŸ‘‘

  • libunwind-ptrace.h β€” header file you include when you're using the interface defined by this library.
  • -lunwind-ptrace -lunwind-generic β€” the linker switches to add when you're building a program that uses the functions defined by this library.

πŸ“ EXAMPLE πŸ“

here's the whole thing in action, copy it, love it, run it:

#include <libunwind-ptrace.h>
#include <stdio.h>
#include <stdlib.h>

int
main (int argc, char **argv)
{
  if (argc != 2) {
    fprintf (stderr, "usage: %s PID\n", argv[0]);
    exit (EXIT_FAILURE);
  }

  char *endptr;
  pid_t target_pid = strtoul (argv[1], &endptr, 10);
  if (target_pid == 0 && argv[1] == endptr) {
    fprintf (stderr, "usage: %s PID\n", argv[0]);
    exit (EXIT_FAILURE);
  }

  unw_addr_space_t as = unw_create_addr_space (&_UPT_accessors, 0);
  if (!as) {
    fprintf (stderr, "unw_create_addr_space() failed");
    exit (EXIT_FAILURE);
  }

  void *ui = _UPT_create (target_pid);
  if (!ui) {
    fprintf (stderr, "_UPT_create() failed");
    exit (EXIT_FAILURE);
  }

  unw_cursor_t cursor;
  int ret = unw_init_remote (&cursor, as, ui);
  if (ret < 0) {
    fprintf (stderr, "unw_init_remote() failed: ret=%d\n", ret);
    exit (EXIT_FAILURE);
  }

  do {
    unw_proc_info_t pi;
    ret = unw_get_proc_info (&cursor, &pi);
    if (ret == -UNW_ENOINFO) {
      fprintf (stdout, "no info\n");
    } else if (ret >= 0) {
      printf ("\tproc=%#016lx-%#016lx\thandler=%#016lx lsda=%#016lx",
              (long) pi.start_ip, (long) pi.end_ip,
              (long) pi.handler, (long) pi.lsda);
    }
    ret = unw_step (&cursor);
  } while (ret > 0);
  if (ret < 0) {
    fprintf (stderr, "unwind failed with ret=%d\n", ret);
    exit (EXIT_FAILURE);
  }

  _UPT_destroy (ui);
  unw_destroy_addr_space (as);
  exit (EXIT_SUCCESS);
}

🧚 SEE ALSO 🧚

execve(2), libunwind(3libunwind), ptrace(2)


πŸ’œ AUTHOR πŸ’œ

David Mosberger-Tang Email: dmosberger@gmail.com WWW: http://www.nongnu.org/libunwind/


and that's the whole page bestie!! this was the Programming Library manual, libunwind-ptrace(3libunwind), dated 29 August 2023 β€” go forth and unwind some other process, you absolute icon πŸ‘‘πŸ’–βœ¨