sysprofd: add support for unwinding without frame pointers

This provides a new sysprof-live-unwinder subprocess that runs as root to
allow accessing all processes on the system via /proc/$pid/. It is spawned
by sysprofd with various perf event FDs and a FD to write captures to.

Ideally the capture_fd is something that will naturally error if the client
application crashes (such as a socketpair() having the peer close). This
is not enforced but encouraged. Additionally, an event_fd is used to allow
the client application to signal the live-unwinder to exit.

Unwinding is performed by looking at the modules loaded into the target
pid and using libdwfl to access DWARF/CFI/etc state machinery. Stack data
does not touch the disk as it exists in a mmap buffer from perf and is
then translated into a callchain and sent to the Sysprof client.

Unwinding occurs as normal post-mortem though is improved through the use
of debuginfod to locate the appropriate symbols.
This commit is contained in:
Christian Hergert
2024-11-03 10:41:44 -08:00
parent 39b96f47f5
commit 1bd79af439
15 changed files with 2580 additions and 4 deletions

View File

@ -39,12 +39,13 @@ need_glib = (need_gtk or
get_option('tools') or
get_option('tests'))
need_libsysprof = (need_gtk or
get_option('sysprofd') == 'bundled' or
get_option('libsysprof') or
get_option('examples') or
get_option('tools') or
get_option('tests'))
dex_req = '0.6'
dex_req = '0.9'
glib_req = '2.76.0'
gtk_req = '4.15'
polkit_req = '0.105'

View File

@ -11,6 +11,8 @@ sysprof_version_conf.set('MINOR_VERSION', sysprof_version[1])
sysprof_version_conf.set('MICRO_VERSION', 0)
sysprof_version_conf.set('VERSION', meson.project_version())
pkglibexecdir = join_paths(get_option('prefix'), get_option('libexecdir'))
subdir('libsysprof-capture')
if need_libsysprof
@ -20,6 +22,7 @@ endif
if get_option('sysprofd') == 'bundled'
subdir('sysprofd')
subdir('sysprof-live-unwinder')
endif
if get_option('gtk')

View File

@ -0,0 +1,752 @@
/*
* main.c
*
* Copyright 2024 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include <stdatomic.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <glib/gstdio.h>
#include <glib-unix.h>
#include <sysprof.h>
#include "sysprof-live-unwinder.h"
#include "sysprof-perf-event-stream-private.h"
#define CAPTURE_BUFFER_SIZE (4096*16)
#define N_PAGES 32
#define DUMP_BYTES(_n, _b, _l) \
G_STMT_START { \
GString *str, *astr; \
gsize _i; \
g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
" %s = %p [%d]", #_n, _b, (gint)_l); \
str = g_string_sized_new (80); \
astr = g_string_sized_new (16); \
for (_i = 0; _i < _l; _i++) \
{ \
if ((_i % 16) == 0) \
g_string_append_printf (str, "%06x: ", (guint)_i); \
g_string_append_printf (str, " %02x", _b[_i]); \
\
if (g_ascii_isprint(_b[_i])) \
g_string_append_printf (astr, " %c", _b[_i]); \
else \
g_string_append (astr, " ."); \
\
if ((_i % 16) == 15) \
{ \
g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
"%s %s", str->str, astr->str); \
str->str[0] = str->len = 0; \
astr->str[0] = astr->len = 0; \
} \
else if ((_i % 16) == 7) \
{ \
g_string_append (str, " "); \
g_string_append (astr, " "); \
} \
} \
\
if (_i != 16) \
g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
"%-56s %s", str->str, astr->str); \
\
g_string_free (str, TRUE); \
g_string_free (astr, TRUE); \
} G_STMT_END
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref)
typedef struct _PerfSource
{
GSource gsource;
SysprofLiveUnwinder *unwinder;
SysprofCaptureWriter *writer;
guint64 map_size;
struct perf_event_mmap_page *map;
guint8 *map_data;
const guint8 *map_data_end;
guint64 map_data_size;
guint64 tail;
guint8 *buffer;
int cpu;
GPid self_pid;
guint stack_size;
} PerfSource;
typedef struct _PerfFDArg
{
int fd;
int cpu;
} PerfFDArg;
SYSPROF_ALIGNED_BEGIN(8);
typedef struct _StackRegs
{
guint64 abi;
guint64 registers[0];
} StackRegs
SYSPROF_ALIGNED_END(8);
SYSPROF_ALIGNED_BEGIN(8);
typedef struct _StackUser
{
guint64 size;
guint8 data[0];
} StackUser
SYSPROF_ALIGNED_END(8);
static GArray *all_perf_fds;
static inline void
realign (gsize *pos,
gsize align)
{
*pos = (*pos + align - 1) & ~(align - 1);
}
static void
handle_event (PerfSource *source,
const SysprofPerfEvent *event)
{
gsize offset;
gint64 time;
g_assert (source != NULL);
g_assert (event != NULL);
switch (event->header.type)
{
case PERF_RECORD_COMM:
offset = strlen (event->comm.comm) + 1;
realign (&offset, sizeof (guint64));
offset += sizeof (GPid) + sizeof (GPid);
memcpy (&time, event->comm.comm + offset, sizeof time);
if (event->comm.pid == event->comm.tid)
{
sysprof_live_unwinder_seen_process (source->unwinder,
time,
source->cpu,
event->comm.pid,
event->comm.comm);
}
break;
case PERF_RECORD_EXIT:
/* Ignore fork exits for now */
if (event->exit.tid != event->exit.pid)
break;
sysprof_live_unwinder_process_exited (source->unwinder,
event->exit.time,
source->cpu,
event->exit.pid);
break;
case PERF_RECORD_FORK:
sysprof_live_unwinder_process_forked (source->unwinder,
event->fork.time,
source->cpu,
event->fork.ptid,
event->fork.tid);
break;
case PERF_RECORD_LOST:
{
char message[64];
g_snprintf (message, sizeof message,
"Lost %"G_GUINT64_FORMAT" samples",
event->lost.lost);
sysprof_capture_writer_add_log (source->writer,
SYSPROF_CAPTURE_CURRENT_TIME,
source->cpu,
-1,
G_LOG_LEVEL_CRITICAL, "Sampler", message);
break;
}
case PERF_RECORD_MMAP:
offset = strlen (event->mmap.filename) + 1;
realign (&offset, sizeof (guint64));
offset += sizeof (GPid) + sizeof (GPid);
memcpy (&time, event->mmap.filename + offset, sizeof time);
sysprof_live_unwinder_track_mmap (source->unwinder,
time,
source->cpu,
event->mmap.pid,
event->mmap.addr,
event->mmap.addr + event->mmap.len,
event->mmap.pgoff,
0,
event->mmap.filename,
NULL);
break;
case PERF_RECORD_MMAP2:
offset = strlen (event->mmap2.filename) + 1;
realign (&offset, sizeof (guint64));
offset += sizeof (GPid) + sizeof (GPid);
memcpy (&time, event->mmap2.filename + offset, sizeof time);
if ((event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) != 0)
{
char build_id[G_N_ELEMENTS (event->mmap2.build_id) * 2 + 1];
guint len = MIN (G_N_ELEMENTS (event->mmap2.build_id), event->mmap2.build_id_size);
for (guint i = 0; i < len; i++)
g_snprintf (&build_id[len*2], 3, "%02x", event->mmap2.build_id[i]);
build_id[len*2] = 0;
sysprof_live_unwinder_track_mmap (source->unwinder,
time,
source->cpu,
event->mmap2.pid,
event->mmap2.addr,
event->mmap2.addr + event->mmap2.len,
event->mmap2.pgoff,
event->mmap2.ino,
event->mmap2.filename,
build_id);
}
else
{
sysprof_live_unwinder_track_mmap (source->unwinder,
time,
source->cpu,
event->mmap2.pid,
event->mmap2.addr,
event->mmap2.addr + event->mmap2.len,
event->mmap2.pgoff,
event->mmap2.ino,
event->mmap2.filename,
NULL);
}
break;
case PERF_RECORD_READ:
break;
case PERF_RECORD_SAMPLE:
{
const guint8 *endptr = (const guint8 *)event + event->header.size;
const guint64 *ips = event->callchain.ips;
int n_ips = event->callchain.n_ips;
guint64 trace[3];
/* We always expect PERF_RECORD_SAMPLE to contain a callchain because
* we need that even if we sample the stack for user-space unwinding.
* Otherwise we lose the blended stack trace.
*/
if (n_ips == 0)
{
if (event->callchain.header.misc & PERF_RECORD_MISC_KERNEL)
{
trace[0] = PERF_CONTEXT_KERNEL;
trace[1] = event->callchain.ip;
trace[2] = PERF_CONTEXT_USER;
ips = trace;
n_ips = 3;
}
else
{
trace[0] = PERF_CONTEXT_USER;
trace[1] = event->callchain.ip;
ips = trace;
n_ips = 2;
}
}
if (source->stack_size && source->stack_size < event->header.size)
{
guint64 dyn_size = *((const guint64 *)endptr - 1);
const StackUser *stack_user = (const StackUser *)(endptr - sizeof (guint64) - source->stack_size - sizeof (StackUser));
const StackRegs *stack_regs = (const StackRegs *)&event->callchain.ips[event->callchain.n_ips];
guint n_regs = ((const guint8 *)stack_user - (const guint8 *)stack_regs->registers) / sizeof (guint64);
#if 0
g_print ("n_ips=%u stack_size=%ld dyn_size=%ld abi=%ld n_regs=%d\n",
n_ips, stack_user->size, dyn_size, stack_regs->abi, n_regs);
#endif
sysprof_live_unwinder_process_sampled_with_stack (source->unwinder,
event->callchain.time,
source->cpu,
event->callchain.pid,
event->callchain.tid,
ips,
n_ips,
stack_user->data,
stack_user->size,
dyn_size,
stack_regs->abi,
stack_regs->registers,
n_regs);
break;
}
sysprof_live_unwinder_process_sampled (source->unwinder,
event->callchain.time,
source->cpu,
event->callchain.pid,
event->callchain.tid,
ips,
n_ips);
break;
}
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
default:
break;
}
}
static gboolean
perf_source_prepare (GSource *gsource,
int *timeout)
{
*timeout = 50;
return FALSE;
}
static gboolean
perf_source_check (GSource *gsource)
{
PerfSource *source = (PerfSource *)gsource;
guint64 head;
guint64 tail;
atomic_thread_fence (memory_order_acquire);
tail = source->tail;
head = source->map->data_head;
if (head < tail)
tail = head;
return (head - tail) >= sizeof (struct perf_event_header);
}
static gboolean
perf_source_dispatch (GSource *gsource,
GSourceFunc callback,
gpointer user_data)
{
PerfSource *source = (PerfSource *)gsource;
guint64 n_bytes = source->map_data_size;
guint64 mask = n_bytes - 1;
guint64 head;
guint64 tail;
guint us = 0;
guint them = 0;
g_assert (source != NULL);
tail = source->tail;
head = source->map->data_head;
atomic_thread_fence (memory_order_acquire);
if (head < tail)
tail = head;
while ((head - tail) >= sizeof (struct perf_event_header))
{
const SysprofPerfEvent *event;
struct perf_event_header *header;
gboolean is_self = FALSE;
/* Note that:
*
* - perf events are a multiple of 64 bits
* - the perf event header is 64 bits
* - the data area is a multiple of 64 bits
*
* which means there will always be space for one header, which means we
* can safely dereference the size field.
*/
header = (struct perf_event_header *)(gpointer)(source->map_data + (tail & mask));
if (header->size > head - tail)
{
/* The kernel did not generate a complete event.
* I don't think that can happen, but we may as well
* be paranoid.
*/
g_warn_if_reached ();
break;
}
if (source->map_data + (tail & mask) + header->size > source->map_data_end)
{
guint8 *b = source->buffer;
gint n_before;
gint n_after;
n_after = (tail & mask) + header->size - n_bytes;
n_before = header->size - n_after;
memcpy (b, source->map_data + (tail & mask), n_before);
memcpy (b + n_before, source->map_data, n_after);
header = (struct perf_event_header *)(gpointer)b;
}
event = (SysprofPerfEvent *)header;
switch (event->header.type)
{
default:
case PERF_RECORD_COMM:
case PERF_RECORD_EXIT:
case PERF_RECORD_FORK:
case PERF_RECORD_MMAP:
case PERF_RECORD_MMAP2:
break;
case PERF_RECORD_SAMPLE:
is_self = event->callchain.pid == source->self_pid;
break;
case PERF_RECORD_READ:
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
goto skip_callback;
case PERF_RECORD_LOST:
break;
}
handle_event (source, event);
us += is_self;
them += !is_self;
skip_callback:
tail += header->size;
}
source->tail = tail;
atomic_thread_fence (memory_order_seq_cst);
source->map->data_tail = tail;
sysprof_capture_writer_flush (source->writer);
return G_SOURCE_CONTINUE;
}
static void
perf_source_finalize (GSource *gsource)
{
PerfSource *source = (PerfSource *)gsource;
if (source->map != NULL &&
(gpointer)source->map != MAP_FAILED)
munmap ((gpointer)source->map, source->map_size);
g_clear_pointer (&source->buffer, g_free);
g_clear_pointer (&source->writer, sysprof_capture_writer_unref);
g_clear_object (&source->unwinder);
source->map = NULL;
source->map_data = NULL;
source->map_data_end = NULL;
source->map_data_size = 0;
source->map_size = 0;
source->tail = 0;
}
static const GSourceFuncs source_funcs = {
.prepare = perf_source_prepare,
.check = perf_source_check,
.dispatch = perf_source_dispatch,
.finalize = perf_source_finalize,
};
static gboolean
perf_source_init (PerfSource *source,
int fd,
SysprofLiveUnwinder *unwinder,
SysprofCaptureWriter *writer,
int cpu,
int stack_size,
GError **error)
{
gsize map_size;
guint8 *map;
g_assert (source != NULL);
g_assert (writer != NULL);
g_assert (SYSPROF_IS_LIVE_UNWINDER (unwinder));
g_assert (fd > STDERR_FILENO);
map_size = N_PAGES * sysprof_getpagesize () + sysprof_getpagesize ();
map = mmap (NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if ((gpointer)map == MAP_FAILED)
{
int errsv = errno;
g_set_error_literal (error,
G_FILE_ERROR,
g_file_error_from_errno (errsv),
g_strerror (errsv));
return FALSE;
}
source->writer = sysprof_capture_writer_ref (writer);
source->unwinder = g_object_ref (unwinder);
source->buffer = g_malloc (map_size);
source->map_size = map_size;
source->map = (gpointer)map;
source->map_data = map + sysprof_getpagesize ();
source->map_data_size = N_PAGES * sysprof_getpagesize ();
source->map_data_end = source->map_data + source->map_data_size;
source->tail = 0;
source->self_pid = getpid ();
source->cpu = cpu;
source->stack_size = stack_size;
g_source_add_unix_fd ((GSource *)source, fd, G_IO_IN);
return TRUE;
}
static GSource *
perf_source_new (int perf_fd,
SysprofLiveUnwinder *unwinder,
SysprofCaptureWriter *writer,
int cpu,
int stack_size,
GError **error)
{
GSource *source;
if (perf_fd <= STDERR_FILENO)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_BADF,
"Invalid file-descriptor for perf event stream");
return NULL;
}
source = g_source_new ((GSourceFuncs *)&source_funcs, sizeof (PerfSource));
g_source_set_static_name (source, "[perf-event-stream]");
g_source_set_priority (source, G_PRIORITY_HIGH);
if (!perf_source_init ((PerfSource *)source, perf_fd, unwinder, writer, cpu, stack_size, error))
g_clear_pointer (&source, g_source_unref);
return source;
}
static void
clear_perf_fd (gpointer data)
{
PerfFDArg *arg = data;
if (arg->fd > STDERR_FILENO)
{
close (arg->fd);
arg->fd = -1;
}
}
static gboolean
perf_fd_callback (const char *option_name,
const char *option_value,
gpointer data,
GError **error)
{
PerfFDArg arg = {-1, -1};
if (sscanf (option_value, "%d:%d", &arg.fd, &arg.cpu) < 1)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_BADF,
"--perf-fd must be in the format FD or FD:CPU_NUMBER");
return FALSE;
}
if (arg.fd <= STDERR_FILENO)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_BADF,
"--perf-fd must be >= %d",
STDERR_FILENO);
return FALSE;
}
g_array_append_val (all_perf_fds, arg);
return TRUE;
}
static void
bump_to_max_fd_limit (void)
{
struct rlimit limit;
if (getrlimit (RLIMIT_NOFILE, &limit) == 0)
{
limit.rlim_cur = limit.rlim_max;
if (setrlimit (RLIMIT_NOFILE, &limit) != 0)
g_warning ("Failed to set FD limit to %"G_GSSIZE_FORMAT"",
(gssize)limit.rlim_max);
else
g_debug ("Set RLIMIT_NOFILE to %"G_GSSIZE_FORMAT"",
(gssize)limit.rlim_max);
}
}
static gboolean
exit_callback (gpointer user_data)
{
g_main_loop_quit (user_data);
return G_SOURCE_REMOVE;
}
int
main (int argc,
char *argv[])
{
g_autoptr(SysprofCaptureWriter) writer = NULL;
g_autoptr(SysprofLiveUnwinder) unwinder = NULL;
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GMainLoop) main_loop = NULL;
g_autoptr(GError) error = NULL;
g_autofd int capture_fd = -1;
g_autofd int kallsyms_fd = -1;
g_autofd int event_fd = -1;
int stack_size = 0;
const GOptionEntry entries[] = {
{ "perf-fd", 0, 0, G_OPTION_ARG_CALLBACK, perf_fd_callback, "A file-descriptor to the perf event stream", "FD[:CPU]" },
{ "capture-fd", 0, 0, G_OPTION_ARG_INT, &capture_fd, "A file-descriptor to the sysprof capture", "FD" },
{ "event-fd", 0, 0, G_OPTION_ARG_INT, &event_fd, "A file-descriptor to an event-fd used to notify unwinder should exit", "FD" },
{ "kallsyms", 'k', 0, G_OPTION_ARG_INT, &kallsyms_fd, "Bundle kallsyms provided from passed FD", "FD" },
{ "stack-size", 's', 0, G_OPTION_ARG_INT, &stack_size, "Size of stacks being recorded", "STACK_SIZE" },
{ 0 }
};
main_loop = g_main_loop_new (NULL, FALSE);
all_perf_fds = g_array_new (FALSE, FALSE, sizeof (PerfFDArg));
g_array_set_clear_func (all_perf_fds, clear_perf_fd);
context = g_option_context_new ("- translate perf event stream to sysprof");
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, "\
This tool is used by sysprofd to process incoming perf events that\n\
include a copy of the stack and register state to the Sysprof capture\n\
format.\n\
\n\
It should be provided two file-descriptors. One is for the perf-event\n\
stream and one is for the Sysprof capture writer.\n\
\n\
Events that are not related to stack traces will also be passed along to\n\
to the capture in the standard Sysprof capture format. That includes mmap\n\
events, process information, and more.\n\
\n\
Examples:\n\
\n\
# FD 3 contains perf_event stream for CPU 1\n\
sysprof-translate --perf-fd=3:1 --capture-fd=4");
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
if (capture_fd <= STDERR_FILENO)
{
g_printerr ("--capture-fd must be > %d\n", STDERR_FILENO);
return EXIT_FAILURE;
}
writer = sysprof_capture_writer_new_from_fd (g_steal_fd (&capture_fd), CAPTURE_BUFFER_SIZE);
if (all_perf_fds->len == 0)
{
g_printerr ("You must secify at least one --perf-fd\n");
return EXIT_FAILURE;
}
bump_to_max_fd_limit ();
unwinder = sysprof_live_unwinder_new (writer, g_steal_fd (&kallsyms_fd));
for (guint i = 0; i < all_perf_fds->len; i++)
{
const PerfFDArg *arg = &g_array_index (all_perf_fds, PerfFDArg, i);
g_autoptr(GSource) perf_source = NULL;
if (!(perf_source = perf_source_new (arg->fd, unwinder, writer, arg->cpu, stack_size, &error)))
{
g_printerr ("Failed to initialize perf event stream: %s\n",
error->message);
return EXIT_FAILURE;
}
g_source_attach (perf_source, NULL);
}
if (event_fd != -1)
{
g_autoptr(GSource) exit_source = g_unix_fd_source_new (event_fd, G_IO_IN);
g_source_set_callback (exit_source,
exit_callback,
g_main_loop_ref (main_loop),
(GDestroyNotify) g_main_loop_unref);
g_source_attach (exit_source, NULL);
}
g_main_loop_run (main_loop);
g_clear_pointer (&all_perf_fds, g_array_unref);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,19 @@
sysprof_live_unwinder_deps = [
libsysprof_static_dep,
dependency('libdw'),
]
sysprof_live_unwinder_sources = [
'sysprof-live-process.c',
'sysprof-live-unwinder.c',
'main.c',
]
sysprof_live_unwinder = executable('sysprof-live-unwinder', sysprof_live_unwinder_sources,
dependencies: sysprof_live_unwinder_deps,
c_args: release_flags,
install: true,
install_dir: pkglibexecdir,
)
subdir('tests')

View File

@ -0,0 +1,505 @@
/*
* sysprof-live-pid.c
*
* Copyright 2024 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/syscall.h>
/* Workaround for linux/fcntl.h also including the
* flock definitions in addition to libc.
*/
#ifndef _LINUX_FCNTL_H
# define _LINUX_FCNTL_H
# include <linux/pidfd.h>
# undef _LINUX_FCNTL_H
#else
# include <linux/pidfd.h>
#endif
#include <linux/perf_event.h>
#include <libelf.h>
#include <elfutils/libdwelf.h>
#include <elfutils/libdwfl.h>
#include <glib/gstdio.h>
#include <sysprof-capture.h>
#include "sysprof-live-process.h"
typedef struct _SysprofLiveProcess
{
Dwfl_Callbacks callbacks;
Dwfl *dwfl;
Elf *elf;
char *root;
GPid pid;
int fd;
} SysprofLiveProcess;
typedef struct _SysprofUnwinder
{
SysprofLiveProcess *process;
const guint8 *stack;
gsize stack_len;
const guint64 *registers;
guint n_registers;
guint64 *addresses;
guint addresses_capacity;
guint addresses_len;
guint64 abi;
guint64 sp;
guint64 pc;
guint64 base_address;
GPid pid;
GPid tid;
} SysprofUnwinder;
static SysprofUnwinder *current_unwinder;
static inline GPid
sysprof_unwinder_next_thread (Dwfl *dwfl,
void *user_data,
void **thread_argp)
{
SysprofUnwinder *unwinder = current_unwinder;
if (*thread_argp == NULL)
{
*thread_argp = unwinder;
return unwinder->tid;
}
return 0;
}
static inline bool
sysprof_unwinder_get_thread (Dwfl *dwfl,
pid_t tid,
void *user_data,
void **thread_argp)
{
SysprofUnwinder *unwinder = current_unwinder;
if (unwinder->tid == tid)
{
*thread_argp = unwinder;
return TRUE;
}
return FALSE;
}
static inline bool
copy_word (const guint8 *data,
Dwarf_Word *result,
guint64 abi)
{
if (abi == PERF_SAMPLE_REGS_ABI_64)
memcpy (result, data, sizeof (guint64));
else if (abi == PERF_SAMPLE_REGS_ABI_32)
memcpy (result, data, sizeof (guint32));
else
g_assert_not_reached ();
return TRUE;
}
static inline bool
sysprof_unwinder_memory_read (Dwfl *dwfl,
Dwarf_Addr addr,
Dwarf_Word *result,
void *user_data)
{
SysprofUnwinder *unwinder = current_unwinder;
if (addr < unwinder->base_address || addr - unwinder->base_address >= unwinder->stack_len)
{
Dwfl_Module *module = NULL;
Elf_Data *data = NULL;
Elf_Scn *section = NULL;
Dwarf_Addr bias;
if (!(module = dwfl_addrmodule (dwfl, addr)) ||
!(section = dwfl_module_address_section (module, &addr, &bias)) ||
!(data = elf_getdata (section, NULL)))
return FALSE;
if (data->d_buf && data->d_size > addr)
return copy_word ((guint8 *)data->d_buf + addr, result, unwinder->abi);
return FALSE;
}
return copy_word (&unwinder->stack[addr - unwinder->base_address], result, unwinder->abi);
}
static inline bool
sysprof_unwinder_set_initial_registers (Dwfl_Thread *thread,
void *user_data)
{
SysprofUnwinder *unwinder = current_unwinder;
dwfl_thread_state_register_pc (thread, unwinder->pc);
if (unwinder->abi == PERF_SAMPLE_REGS_ABI_64)
{
static const int regs_x86_64[] = {0, 3, 2, 1, 4, 5, 6, 7/*sp*/, 9, 10, 11, 12, 13, 14, 15, 16, 8/*ip*/};
for (int i = 0; i < G_N_ELEMENTS (regs_x86_64); i++)
{
int j = regs_x86_64[i];
dwfl_thread_state_registers (thread, i, 1, &unwinder->registers[j]);
}
}
else if (unwinder->abi == PERF_SAMPLE_REGS_ABI_32)
{
static const int regs_i386[] = {0, 2, 3, 1, 7/*sp*/, 6, 4, 5, 8/*ip*/};
for (int i = 0; i < G_N_ELEMENTS (regs_i386); i++)
{
int j = regs_i386[i];
dwfl_thread_state_registers (thread, i, 1, &unwinder->registers[j]);
}
}
return TRUE;
}
static const Dwfl_Thread_Callbacks thread_callbacks = {
sysprof_unwinder_next_thread,
sysprof_unwinder_get_thread,
sysprof_unwinder_memory_read,
sysprof_unwinder_set_initial_registers,
NULL, /* detach */
NULL, /* thread_detach */
};
static inline int
sysprof_unwinder_frame_cb (Dwfl_Frame *frame,
void *user_data)
{
SysprofUnwinder *unwinder = current_unwinder;
Dwarf_Addr pc;
Dwarf_Addr sp;
bool is_activation;
guint8 sp_register_id;
if (unwinder->addresses_len == unwinder->addresses_capacity)
return DWARF_CB_ABORT;
if (!dwfl_frame_pc (frame, &pc, &is_activation))
return DWARF_CB_ABORT;
if (unwinder->abi == PERF_SAMPLE_REGS_ABI_64)
sp_register_id = 7;
else if (unwinder->abi == PERF_SAMPLE_REGS_ABI_32)
sp_register_id = 4;
else
return DWARF_CB_ABORT;
if (dwfl_frame_reg (frame, sp_register_id, &sp) < 0)
return DWARF_CB_ABORT;
unwinder->addresses[unwinder->addresses_len++] = pc;
return DWARF_CB_OK;
}
static inline guint
sysprof_unwind (SysprofLiveProcess *self,
Dwfl *dwfl,
Elf *elf,
GPid pid,
GPid tid,
guint64 abi,
const guint64 *registers,
guint n_registers,
const guint8 *stack,
gsize stack_len,
guint64 *addresses,
guint n_addresses)
{
#if defined(__x86_64__) || defined(__i386__)
SysprofUnwinder unwinder;
g_assert (dwfl != NULL);
g_assert (elf != NULL);
/* Ignore anything byt 32/64 defined abi */
if (!(abi == PERF_SAMPLE_REGS_ABI_32 || abi == PERF_SAMPLE_REGS_ABI_64))
return 0;
/* Make sure we have registers/stack to work with */
if (registers == NULL || stack == NULL || stack_len == 0)
return 0;
/* 9 registers on 32-bit x86, 17 on 64-bit x86_64 */
if (!((abi == PERF_SAMPLE_REGS_ABI_32 && n_registers == 9) ||
(abi == PERF_SAMPLE_REGS_ABI_64 && n_registers == 17)))
return 0;
unwinder.process = self;
unwinder.sp = registers[7];
unwinder.pc = registers[8];
unwinder.base_address = unwinder.sp;
unwinder.addresses = addresses;
unwinder.addresses_capacity = n_addresses;
unwinder.addresses_len = 0;
unwinder.pid = pid;
unwinder.tid = tid;
unwinder.stack = stack;
unwinder.stack_len = stack_len;
unwinder.abi = abi;
unwinder.registers = registers;
unwinder.n_registers = n_registers;
current_unwinder = &unwinder;
dwfl_getthread_frames (dwfl, tid, sysprof_unwinder_frame_cb, NULL);
current_unwinder = NULL;
return unwinder.addresses_len;
#else
return 0;
#endif
}
G_GNUC_NO_INLINE static int
_pidfd_open (int pid,
unsigned flags)
{
int pidfd = syscall (SYS_pidfd_open, pid, flags);
if (pidfd != -1)
{
int old_flags = fcntl (pidfd, F_GETFD);
if (old_flags != -1)
fcntl (pidfd, F_SETFD, old_flags | FD_CLOEXEC);
}
return pidfd;
}
static int
sysprof_live_process_find_elf (Dwfl_Module *module,
void **user_data,
const char *module_name,
Dwarf_Addr base_addr,
char **filename,
Elf **elf)
{
g_assert (current_unwinder != NULL);
g_assert (current_unwinder->process != NULL);
*filename = NULL;
*elf = NULL;
if (module_name[0] == '/')
{
g_autofree char *path = g_strdup_printf ("/proc/%u/root/%s", current_unwinder->pid, module_name);
g_autofd int fd = open (path, O_RDONLY | O_CLOEXEC);
if (fd != -1)
{
*elf = dwelf_elf_begin (fd);
return g_steal_fd (&fd);
}
}
return dwfl_linux_proc_find_elf (module, user_data, module_name, base_addr, filename, elf);
}
static int
sysprof_live_process_find_debuginfo (Dwfl_Module *module,
void **user_data,
const char *module_name,
Dwarf_Addr base_addr,
const char *file_name,
const char *debuglink_file,
GElf_Word debuglink_crc,
char **debuginfo_file_name)
{
return -1;
}
SysprofLiveProcess *
sysprof_live_process_new (GPid pid)
{
SysprofLiveProcess *live_process;
live_process = g_atomic_rc_box_new0 (SysprofLiveProcess);
live_process->pid = pid;
live_process->fd = _pidfd_open (pid, 0);
live_process->root = g_strdup_printf ("/proc/%u/root/", pid);
live_process->callbacks.find_elf = sysprof_live_process_find_elf;
live_process->callbacks.find_debuginfo = sysprof_live_process_find_debuginfo;
live_process->callbacks.debuginfo_path = g_new0 (char *, 2);
live_process->callbacks.debuginfo_path[0] = g_build_filename (live_process->root, "usr/lib/debug", NULL);
return live_process;
}
SysprofLiveProcess *
sysprof_live_process_ref (SysprofLiveProcess *live_process)
{
g_return_val_if_fail (live_process != NULL, NULL);
return g_atomic_rc_box_acquire (live_process);
}
static void
sysprof_live_process_finalize (gpointer data)
{
SysprofLiveProcess *live_process = data;
if (live_process->fd != -1)
{
close (live_process->fd);
live_process->fd = -1;
}
g_clear_pointer (&live_process->elf, elf_end);
g_clear_pointer (&live_process->dwfl, dwfl_end);
g_clear_pointer (&live_process->root, g_free);
g_clear_pointer (&live_process->callbacks.debuginfo_path, g_free);
}
void
sysprof_live_process_unref (SysprofLiveProcess *live_process)
{
g_return_if_fail (live_process != NULL);
g_atomic_rc_box_release_full (live_process, sysprof_live_process_finalize);
}
gboolean
sysprof_live_process_is_active (SysprofLiveProcess *self)
{
g_return_val_if_fail (self != NULL, FALSE);
return self->fd > -1;
}
static Dwfl *
sysprof_live_process_get_dwfl (SysprofLiveProcess *self)
{
g_assert (self != NULL);
if G_UNLIKELY (self->dwfl == NULL)
{
self->dwfl = dwfl_begin (&self->callbacks);
dwfl_linux_proc_report (self->dwfl, self->pid);
dwfl_report_end (self->dwfl, NULL, NULL);
if (self->fd > -1)
{
char path[64];
g_autofd int exe_fd = -1;
g_snprintf (path, sizeof path, "/proc/%u/exe", self->pid);
exe_fd = open (path, O_RDONLY);
if (exe_fd > -1)
{
self->elf = elf_begin (exe_fd, ELF_C_READ_MMAP, NULL);
if (self->elf != NULL)
dwfl_attach_state (self->dwfl, self->elf, self->pid, &thread_callbacks, self);
}
}
else
g_warning ("Attmpting to load exited process\n");
}
return self->dwfl;
}
guint
sysprof_live_process_unwind (SysprofLiveProcess *self,
GPid tid,
guint64 abi,
const guint8 *stack,
gsize stack_len,
const guint64 *registers,
guint n_registers,
guint64 *addresses,
guint n_addresses)
{
#if defined(__x86_64__) || defined(__i386__)
Dwfl *dwfl;
g_assert (self != NULL);
g_assert (stack != NULL);
g_assert (registers != NULL);
g_assert (addresses != NULL);
if (!sysprof_live_process_is_active (self))
return 0;
if (!(dwfl = sysprof_live_process_get_dwfl (self)))
return 0;
if (self->elf == NULL)
return 0;
g_assert (self->dwfl != NULL);
g_assert (self->elf != NULL);
return sysprof_unwind (self,
self->dwfl,
self->elf,
self->pid,
tid,
abi,
registers,
n_registers,
stack,
stack_len,
addresses,
n_addresses);
#else
return 0;
#endif
}
void
sysprof_live_process_add_map (SysprofLiveProcess *self,
guint64 begin,
guint64 end,
guint64 offset,
guint64 inode,
const char *filename)
{
g_assert (self != NULL);
/* We'll reparse VMAs on next use */
g_clear_pointer (&self->dwfl, dwfl_end);
g_clear_pointer (&self->elf, elf_end);
}

View File

@ -0,0 +1,50 @@
/*
* sysprof-live-pid.h
*
* Copyright 2024 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <glib.h>
G_BEGIN_DECLS
typedef struct _SysprofLiveProcess SysprofLiveProcess;
SysprofLiveProcess *sysprof_live_process_new (GPid pid);
SysprofLiveProcess *sysprof_live_process_ref (SysprofLiveProcess *self);
void sysprof_live_process_unref (SysprofLiveProcess *self);
gboolean sysprof_live_process_is_active (SysprofLiveProcess *self);
void sysprof_live_process_add_map (SysprofLiveProcess *self,
guint64 begin,
guint64 end,
guint64 offset,
guint64 inode,
const char *filename);
guint sysprof_live_process_unwind (SysprofLiveProcess *self,
GPid tid,
guint64 abi,
const guint8 *stack,
gsize stack_len,
const guint64 *registers,
guint n_registers,
guint64 *addresses,
guint n_addresses);
G_END_DECLS

View File

@ -0,0 +1,427 @@
/*
* sysprof-live-unwinder.c
*
* Copyright 2024 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include <fcntl.h>
#include <unistd.h>
#include <linux/perf_event.h>
#include <glib/gstdio.h>
#include "sysprof-live-process.h"
#include "sysprof-live-unwinder.h"
#include "sysprof-maps-parser-private.h"
struct _SysprofLiveUnwinder
{
GObject parent_instance;
SysprofCaptureWriter *writer;
GHashTable *live_pids_by_pid;
};
G_DEFINE_FINAL_TYPE (SysprofLiveUnwinder, sysprof_live_unwinder, G_TYPE_OBJECT)
enum {
CLOSED,
N_SIGNALS
};
static guint signals[N_SIGNALS];
static char *
sysprof_live_unwinder_read_file (SysprofLiveUnwinder *self,
const char *path,
gboolean insert_into_capture)
{
gint64 when = SYSPROF_CAPTURE_CURRENT_TIME;
char *contents = NULL;
gsize len = 0;
gsize offset = 0;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
g_assert (self->writer != NULL);
if (!g_file_get_contents (path, &contents, &len, NULL))
return NULL;
if (insert_into_capture)
{
while (len > 0)
{
gsize this_write = MIN (len, 4096*4);
if (!sysprof_capture_writer_add_file (self->writer,
when,
-1,
-1,
path,
this_write == len,
(const guint8 *)&contents[offset], this_write))
break;
len -= this_write;
offset += this_write;
}
}
return contents;
}
static char *
sysprof_live_unwinder_read_pid_file (SysprofLiveUnwinder *self,
GPid pid,
const char *path_part,
gboolean insert_into_capture)
{
g_autofree char *path = NULL;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
g_assert (self->writer != NULL);
path = g_strdup_printf ("/proc/%d/%s", pid, path_part);
return sysprof_live_unwinder_read_file (self, path, insert_into_capture);
}
static SysprofLiveProcess *
sysprof_live_unwinder_find_pid (SysprofLiveUnwinder *self,
GPid pid,
gboolean send_comm)
{
SysprofLiveProcess *live_pid;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
if (pid < 0)
return NULL;
if (!(live_pid = g_hash_table_lookup (self->live_pids_by_pid, GINT_TO_POINTER (pid))))
{
gint64 now = SYSPROF_CAPTURE_CURRENT_TIME;
live_pid = sysprof_live_process_new (pid);
g_hash_table_replace (self->live_pids_by_pid, GINT_TO_POINTER (pid), live_pid);
if (send_comm)
{
g_autofree char *path = g_strdup_printf ("/proc/%d/comm", pid);
g_autofree char *comm = NULL;
gsize len;
if (g_file_get_contents (path, &comm, &len, NULL))
{
g_autofree char *tmp = comm;
comm = g_strstrip (g_utf8_make_valid (tmp, len));
sysprof_capture_writer_add_process (self->writer, now, -1, pid, comm);
}
}
if (sysprof_live_process_is_active (live_pid))
{
g_autofree char *mountinfo = sysprof_live_unwinder_read_pid_file (self, pid, "mountinfo", TRUE);
g_autofree char *maps = sysprof_live_unwinder_read_pid_file (self, pid, "maps", FALSE);
if (maps != NULL)
{
SysprofMapsParser maps_parser;
guint64 begin, end, offset, inode;
char *filename;
sysprof_maps_parser_init (&maps_parser, maps, -1);
while (sysprof_maps_parser_next (&maps_parser, &begin, &end, &offset, &inode, &filename))
{
sysprof_live_process_add_map (live_pid, begin, end, offset, inode, filename);
sysprof_capture_writer_add_map (self->writer, now, -1, pid,
begin, end, offset,
inode, filename);
g_free (filename);
}
}
}
}
return live_pid;
}
static void
sysprof_live_unwinder_mine_pids (SysprofLiveUnwinder *self)
{
g_autoptr(GDir) dir = NULL;
const char *name;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
g_assert (self->writer != NULL);
if (!(dir = g_dir_open ("/proc", 0, NULL)))
return;
while ((name = g_dir_read_name (dir)))
{
GPid pid;
if (!g_ascii_isdigit (*name))
continue;
if (!(pid = atoi (name)))
continue;
sysprof_live_unwinder_find_pid (self, pid, TRUE);
}
}
static void
sysprof_live_unwinder_finalize (GObject *object)
{
SysprofLiveUnwinder *self = (SysprofLiveUnwinder *)object;
g_clear_pointer (&self->writer, sysprof_capture_writer_unref);
g_clear_pointer (&self->live_pids_by_pid, g_hash_table_unref);
G_OBJECT_CLASS (sysprof_live_unwinder_parent_class)->finalize (object);
}
static void
sysprof_live_unwinder_class_init (SysprofLiveUnwinderClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sysprof_live_unwinder_finalize;
signals[CLOSED] =
g_signal_new ("closed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
}
static void
sysprof_live_unwinder_init (SysprofLiveUnwinder *self)
{
self->live_pids_by_pid = g_hash_table_new_full (NULL,
NULL,
NULL,
(GDestroyNotify)sysprof_live_process_unref);
}
SysprofLiveUnwinder *
sysprof_live_unwinder_new (SysprofCaptureWriter *writer,
int kallsyms_fd)
{
SysprofLiveUnwinder *self;
g_autofree char *mounts = NULL;
g_return_val_if_fail (writer != NULL, NULL);
self = g_object_new (SYSPROF_TYPE_LIVE_UNWINDER, NULL);
self->writer = sysprof_capture_writer_ref (writer);
if (kallsyms_fd != -1)
{
sysprof_capture_writer_add_file_fd (writer,
SYSPROF_CAPTURE_CURRENT_TIME,
-1,
-1,
"/proc/kallsyms",
kallsyms_fd);
close (kallsyms_fd);
}
mounts = sysprof_live_unwinder_read_file (self, "/proc/mounts", TRUE);
sysprof_live_unwinder_mine_pids (self);
return self;
}
void
sysprof_live_unwinder_seen_process (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
const char *comm)
{
G_GNUC_UNUSED SysprofLiveProcess *live_pid;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
live_pid = sysprof_live_unwinder_find_pid (self, pid, FALSE);
sysprof_capture_writer_add_process (self->writer, time, cpu, pid, comm);
}
void
sysprof_live_unwinder_process_exited (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid)
{
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
sysprof_capture_writer_add_exit (self->writer, time, cpu, pid);
}
void
sysprof_live_unwinder_process_forked (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid parent_tid,
GPid tid)
{
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
sysprof_capture_writer_add_fork (self->writer, time, cpu, parent_tid, tid);
}
void
sysprof_live_unwinder_track_mmap (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
SysprofCaptureAddress begin,
SysprofCaptureAddress end,
SysprofCaptureAddress offset,
guint64 inode,
const char *filename,
const char *build_id)
{
G_GNUC_UNUSED SysprofLiveProcess *live_pid;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE);
if (build_id != NULL)
sysprof_capture_writer_add_map_with_build_id (self->writer, time, cpu, pid,
begin, end, offset,
inode, filename,
build_id);
else
sysprof_capture_writer_add_map (self->writer, time, cpu, pid,
begin, end, offset,
inode, filename);
sysprof_live_process_add_map (live_pid, begin, end, offset, inode, filename);
}
void
sysprof_live_unwinder_process_sampled (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
GPid tid,
const SysprofCaptureAddress *addresses,
guint n_addresses)
{
G_GNUC_UNUSED SysprofLiveProcess *live_pid;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE);
sysprof_capture_writer_add_sample (self->writer, time, cpu, pid, tid,
addresses, n_addresses);
}
void
sysprof_live_unwinder_process_sampled_with_stack (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
GPid tid,
const SysprofCaptureAddress *addresses,
guint n_addresses,
const guint8 *stack,
guint64 stack_size,
guint64 stack_dyn_size,
guint64 abi,
const guint64 *registers,
guint n_registers)
{
SysprofLiveProcess *live_pid;
SysprofCaptureAddress unwound[256];
gboolean found_user = FALSE;
guint pos;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
g_assert (stack != NULL);
g_assert (stack_dyn_size <= stack_size);
if (stack_dyn_size == 0 || n_addresses >= G_N_ELEMENTS (unwound))
{
sysprof_live_unwinder_process_sampled (self, time, cpu, pid, tid, addresses, n_addresses);
return;
}
live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE);
/* Copy addresses over (which might be kernel, context-switch, etc until
* we get to the PERF_CONTEXT_USER. We'll decode the stack right into the
* location after that.
*/
for (pos = 0; pos < n_addresses; pos++)
{
unwound[pos] = addresses[pos];
if (addresses[pos] == PERF_CONTEXT_USER)
{
found_user = TRUE;
break;
}
}
/* If we didn't find a user context (but we have a stack size) synthesize
* the PERF_CONTEXT_USER now.
*/
if (!found_user && pos < G_N_ELEMENTS (unwound))
unwound[pos++] = PERF_CONTEXT_USER;
/* Now request the live process unwind the user-space stack */
if (pos < G_N_ELEMENTS (unwound))
{
guint n_unwound;
n_unwound = sysprof_live_process_unwind (live_pid,
tid,
abi,
stack,
stack_dyn_size,
registers,
n_registers,
&unwound[pos],
G_N_ELEMENTS (unwound) - pos);
/* Only take DWARF unwind if it was better */
if (pos + n_unwound > n_addresses)
{
addresses = unwound;
n_addresses = pos + n_unwound;
}
}
sysprof_capture_writer_add_sample (self->writer, time, cpu, pid, tid, addresses, n_addresses);
}

View File

@ -0,0 +1,79 @@
/*
* sysprof-live-unwinder.h
*
* Copyright 2024 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <sysprof.h>
G_BEGIN_DECLS
#define SYSPROF_TYPE_LIVE_UNWINDER (sysprof_live_unwinder_get_type())
G_DECLARE_FINAL_TYPE (SysprofLiveUnwinder, sysprof_live_unwinder, SYSPROF, LIVE_UNWINDER, GObject)
SysprofLiveUnwinder *sysprof_live_unwinder_new (SysprofCaptureWriter *writer,
int kallsyms_fd);
void sysprof_live_unwinder_seen_process (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
const char *comm);
void sysprof_live_unwinder_process_exited (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid);
void sysprof_live_unwinder_process_forked (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid parent_tid,
GPid tid);
void sysprof_live_unwinder_track_mmap (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
SysprofCaptureAddress begin,
SysprofCaptureAddress end,
SysprofCaptureAddress offset,
guint64 inode,
const char *filename,
const char *build_id);
void sysprof_live_unwinder_process_sampled (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
GPid tid,
const SysprofCaptureAddress *addresses,
guint n_addresses);
void sysprof_live_unwinder_process_sampled_with_stack (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
GPid tid,
const SysprofCaptureAddress *addresses,
guint n_addresses,
const guint8 *stack,
guint64 stack_size,
guint64 stack_dyn_size,
guint64 abi,
const guint64 *registers,
guint n_registers);
G_END_DECLS

View File

@ -0,0 +1,35 @@
sysprof_live_unwinder_test_env = [
'G_DEBUG=gc-friendly',
'GSETTINGS_BACKEND=memory',
'MALLOC_CHECK_=2',
]
sysprof_live_unwinder_testsuite_c_args = [
'-DG_ENABLE_DEBUG',
'-UG_DISABLE_ASSERT',
'-UG_DISABLE_CAST_CHECKS',
'-DBUILDDIR="@0@"'.format(meson.current_build_dir()),
]
sysprof_live_unwinder_testsuite = {
'test-live-unwinder' : {'skip': true},
}
sysprof_live_unwinder_testsuite_deps = [
libsysprof_static_dep,
]
if polkit_agent_dep.found()
sysprof_live_unwinder_testsuite_deps += polkit_agent_dep
endif
foreach test, params: sysprof_live_unwinder_testsuite
test_exe = executable(test, '@0@.c'.format(test),
c_args: sysprof_live_unwinder_testsuite_c_args,
dependencies: sysprof_live_unwinder_testsuite_deps,
)
if not params.get('skip', false)
test(test, test_exe, env: sysprof_live_unwinder_test_env)
endif
endforeach

View File

@ -0,0 +1,391 @@
/*
* test-live-unwinder.c
*
* Copyright 2024 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <libdex.h>
#include <sysprof.h>
#include "sysprof-perf-event-stream-private.h"
#if HAVE_POLKIT_AGENT
# define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
# include <polkit/polkit.h>
# include <polkitagent/polkitagent.h>
#endif
#include <asm/perf_regs.h>
#define N_WAKEUP_EVENTS 149
/* The following was provided to Sysprof by Serhei Makarov as part
* of the eu-stacktrace prototype work.
*/
#ifdef _ASM_X86_PERF_REGS_H
/* #define SYSPROF_ARCH_PREFERRED_REGS PERF_REG_EXTENDED_MASK -- error on x86_64 due to including segment regs*/
#define REG(R) (1ULL << PERF_REG_X86_ ## R)
#define DWARF_NEEDED_REGS (/* no FLAGS */ REG(IP) | REG(SP) | REG(AX) | REG(CX) | REG(DX) | REG(BX) | REG(SI) | REG(DI) | REG(SP) | REG(BP) | /* no segment regs */ REG(R8) | REG(R9) | REG(R10) | REG(R11) | REG(R12) | REG(R13) | REG(R14) | REG(R15))
/* XXX register ordering is defined in linux arch/x86/include/uapi/asm/perf_regs.h;
see code in tools/perf/util/intel-pt.c intel_pt_add_gp_regs()
and note how registers are added in the same order as the perf_regs.h enum */
#define SYSPROF_ARCH_PREFERRED_REGS DWARF_NEEDED_REGS
/* TODO: add other architectures, imitating the linux tools/perf tree */
#else
# define SYSPROF_ARCH_PREFERRED_REGS PERF_REG_EXTENDED_MASK
#endif /* _ASM_{arch}_PERF_REGS_H */
static gboolean sample_stack;
static char *kallsyms = NULL;
static int sample_stack_size = 8192;
static void
open_perf_stream_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GDBusConnection *connection = (GDBusConnection *)object;
g_autoptr(DexPromise) promise = user_data;
g_autoptr(GUnixFDList) fd_list = NULL;
g_autoptr(GVariant) ret = NULL;
g_autoptr(GError) error = NULL;
g_assert (G_IS_DBUS_CONNECTION (connection));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (DEX_IS_PROMISE (promise));
if ((ret = g_dbus_connection_call_with_unix_fd_list_finish (connection, &fd_list, result, &error)))
{
int handle;
int fd;
g_variant_get (ret, "(h)", &handle);
if (-1 != (fd = g_unix_fd_list_get (fd_list, handle, &error)))
{
dex_promise_resolve_fd (promise, g_steal_fd (&fd));
return;
}
}
g_assert (error != NULL);
dex_promise_reject (promise, g_steal_pointer (&error));
}
static int
open_perf_stream (GDBusConnection *bus,
int cpu,
GError **error)
{
struct perf_event_attr attr = {0};
gboolean with_mmap2 = TRUE;
gboolean use_software = FALSE;
g_assert (G_IS_DBUS_CONNECTION (bus));
g_assert (cpu >= 0);
g_assert (error != NULL);
try_again:
attr.sample_type = PERF_SAMPLE_IP
| PERF_SAMPLE_TID
| PERF_SAMPLE_IDENTIFIER
| PERF_SAMPLE_CALLCHAIN
| PERF_SAMPLE_TIME;
if (sample_stack)
{
attr.sample_type |= (PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
attr.sample_stack_user = sample_stack_size;
attr.sample_regs_user = SYSPROF_ARCH_PREFERRED_REGS;
}
attr.wakeup_events = N_WAKEUP_EVENTS;
attr.disabled = TRUE;
attr.mmap = TRUE;
attr.mmap2 = with_mmap2;
attr.comm = 1;
attr.task = 1;
attr.exclude_idle = 1;
attr.sample_id_all = 1;
#ifdef HAVE_PERF_CLOCKID
attr.clockid = sysprof_clock;
attr.use_clockid = 1;
#endif
attr.size = sizeof attr;
if (use_software)
{
attr.type = PERF_TYPE_SOFTWARE;
attr.config = PERF_COUNT_SW_CPU_CLOCK;
attr.sample_period = 1000000;
}
else
{
attr.type = PERF_TYPE_HARDWARE;
attr.config = PERF_COUNT_HW_CPU_CYCLES;
attr.sample_period = 1200000;
}
{
g_autoptr(GVariant) options = _sysprof_perf_event_attr_to_variant (&attr);
g_autoptr(DexPromise) promise = dex_promise_new ();
g_autofd int fd = -1;
g_dbus_connection_call_with_unix_fd_list (bus,
"org.gnome.Sysprof3",
"/org/gnome/Sysprof3",
"org.gnome.Sysprof3.Service",
"PerfEventOpen",
g_variant_new ("(@a{sv}iiht)",
options,
-1,
cpu,
-1,
0),
G_VARIANT_TYPE ("(h)"),
G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
G_MAXUINT,
NULL,
dex_promise_get_cancellable (promise),
open_perf_stream_cb,
dex_ref (promise));
fd = dex_await_fd (dex_ref (promise), error);
if (*error == NULL)
{
g_printerr ("CPU[%d]: opened perf_event stream as FD %d\n", cpu, fd);
return g_steal_fd (&fd);
}
fd = -1;
}
if (with_mmap2)
{
g_clear_error (error);
with_mmap2 = FALSE;
goto try_again;
}
if (use_software == FALSE)
{
g_clear_error (error);
with_mmap2 = TRUE;
use_software = TRUE;
goto try_again;
}
g_assert (*error != NULL);
return -1;
}
static DexFuture *
main_fiber (gpointer user_data)
{
g_autoptr(GSubprocessLauncher) launcher = NULL;
g_autoptr(GDBusConnection) bus = NULL;
g_autoptr(GSubprocess) subprocess = NULL;
g_autoptr(GPtrArray) argv = NULL;
g_autoptr(GError) error = NULL;
g_autofd int writer_fd = -1;
int n_cpu = g_get_num_processors ();
int next_target_fd = 3;
/* Get our bus we will use for authorization */
if (!(bus = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error)))
return dex_future_new_for_error (g_steal_pointer (&error));
/* Setup our launcher we'll use to map FDs into the translator */
launcher = g_subprocess_launcher_new (0);
/* Setup our argv which will notify the child about where to
* find the FDs containing perf event streams.
*/
argv = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (argv, g_build_filename (BUILDDIR, "..", "sysprof-live-unwinder", NULL));
/* Provide kallsyms from the file provided */
if (kallsyms != NULL)
{
int fd = open (kallsyms, O_RDONLY | O_CLOEXEC);
if (fd != -1)
{
g_subprocess_launcher_take_fd (launcher, fd, next_target_fd);
g_ptr_array_add (argv, g_strdup_printf ("--kallsyms=%d", next_target_fd++));
}
}
if (sample_stack)
g_ptr_array_add (argv, g_strdup_printf ("--stack-size=%u", sample_stack_size));
g_printerr ("sysprof-live-unwinder at %s\n", (const char *)argv->pdata[0]);
/* First try to open a perf_event stream for as many CPUs as we
* can before we get complaints from the kernel.
*/
for (int cpu = 0; cpu < n_cpu; cpu++)
{
g_autoptr(GError) cpu_error = NULL;
g_autofd int perf_fd = open_perf_stream (bus, cpu, &cpu_error);
if (perf_fd == -1)
{
g_printerr ("CPU[%d]: %s\n", cpu, cpu_error->message);
continue;
}
if (0 != ioctl (perf_fd, PERF_EVENT_IOC_ENABLE))
{
int errsv = errno;
g_warning ("Failed to enable perf_fd: %s", g_strerror (errsv));
}
g_ptr_array_add (argv, g_strdup_printf ("--perf-fd=%d:%d", next_target_fd, cpu));
g_subprocess_launcher_take_fd (launcher, g_steal_fd (&perf_fd), next_target_fd);
next_target_fd++;
}
/* Now create a FD for our destination capture. */
if (-1 == (writer_fd = open ("translated.syscap", O_CREAT|O_RDWR|O_CLOEXEC, 0664)) ||
ftruncate (writer_fd, 0) != 0)
return dex_future_new_for_errno (errno);
g_ptr_array_add (argv, g_strdup_printf ("--capture-fd=%d", next_target_fd));
g_subprocess_launcher_take_fd (launcher, g_steal_fd (&writer_fd), next_target_fd);
next_target_fd++;
/* Null-terminate our argv */
g_ptr_array_add (argv, NULL);
/* Spawn our worker process with the perf FDs and writer provided */
if (!(subprocess = g_subprocess_launcher_spawnv (launcher,
(const char * const *)argv->pdata,
&error)))
return dex_future_new_for_error (g_steal_pointer (&error));
/* Now wait for the translation process to complete */
if (!dex_await_boolean (dex_subprocess_wait_check (subprocess), &error))
return dex_future_new_for_error (g_steal_pointer (&error));
return dex_future_new_true ();
}
static DexFuture *
finally_cb (DexFuture *future,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
GMainLoop *main_loop = user_data;
if (!dex_await (dex_ref (future), &error))
{
g_printerr ("Error: %s\n", error->message);
exit (EXIT_FAILURE);
}
g_main_loop_quit (main_loop);
return NULL;
}
int
main (int argc,
char *argv[])
{
#if HAVE_POLKIT_AGENT
PolkitAgentListener *polkit = NULL;
PolkitSubject *subject = NULL;
#endif
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GMainLoop) main_loop = NULL;
g_autoptr(GError) error = NULL;
DexFuture *future;
GOptionEntry entries[] = {
{ "sample-stack", 's', 0, G_OPTION_ARG_NONE, &sample_stack, "If the stack should be sampled for user-space unwinding" },
{ "sample-stack-size", 'S', 0, G_OPTION_ARG_INT, &sample_stack_size, "If size of the stack to sample in bytes" },
{ "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms, "Specify kallsyms for use" },
{ NULL }
};
sysprof_clock_init ();
dex_init ();
main_loop = g_main_loop_new (NULL, FALSE);
context = g_option_context_new ("- test sysprof-live-unwinder");
g_option_context_add_main_entries (context, entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
#if HAVE_POLKIT_AGENT
/* Start polkit agent so that we can elevate privileges from a TTY */
if (g_getenv ("DESKTOP_SESSION") == NULL &&
(subject = polkit_unix_process_new_for_owner (getpid (), 0, -1)))
{
g_autoptr(GError) pkerror = NULL;
polkit = polkit_agent_text_listener_new (NULL, NULL);
polkit_agent_listener_register (polkit,
POLKIT_AGENT_REGISTER_FLAGS_NONE,
subject,
NULL,
NULL,
&pkerror);
if (pkerror != NULL)
{
g_dbus_error_strip_remote_error (pkerror);
g_printerr ("Failed to register polkit agent: %s\n",
pkerror->message);
}
}
#endif
future = dex_scheduler_spawn (NULL, 0, main_fiber, NULL, NULL);
future = dex_future_finally (future,
finally_cb,
g_main_loop_ref (main_loop),
(GDestroyNotify) g_main_loop_unref);
dex_future_disown (future);
g_main_loop_run (main_loop);
g_clear_pointer (&kallsyms, g_free);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,247 @@
/*
* ipc-unwinder-impl.c
*
* Copyright 2024 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ipc-unwinder-impl"
#include "config.h"
#include <errno.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <glib/gstdio.h>
#include <polkit/polkit.h>
#include "ipc-unwinder-impl.h"
struct _IpcUnwinderImpl
{
IpcUnwinderSkeleton parent_instance;
};
static void
child_setup (gpointer data)
{
prctl (PR_SET_PDEATHSIG, SIGKILL);
}
static gboolean
ipc_unwinder_impl_handle_unwind (IpcUnwinder *unwinder,
GDBusMethodInvocation *invocation,
GUnixFDList *fd_list,
guint stack_size,
GVariant *arg_perf_fds,
GVariant *arg_event_fd)
{
g_autoptr(GSubprocessLauncher) launcher = NULL;
g_autoptr(GSubprocess) subprocess = NULL;
g_autoptr(GUnixFDList) out_fd_list = NULL;
g_autoptr(GPtrArray) argv = NULL;
g_autoptr(GError) error = NULL;
g_autofd int our_fd = -1;
g_autofd int their_fd = -1;
g_autofd int event_fd = -1;
GVariantIter iter;
int capture_fd_handle;
int pair[2];
int next_target_fd = 3;
int perf_fd_handle;
int cpu;
g_assert (IPC_IS_UNWINDER_IMPL (unwinder));
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
g_assert (!fd_list || G_IS_UNIX_FD_LIST (fd_list));
if (stack_size == 0 || stack_size % sysconf (_SC_PAGESIZE) != 0)
{
g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation),
G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"Stack size must be a multiple of the page size");
return TRUE;
}
if (fd_list == NULL)
{
g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation),
G_DBUS_ERROR,
G_DBUS_ERROR_FILE_NOT_FOUND,
"Missing perf FDs");
return TRUE;
}
launcher = g_subprocess_launcher_new (0);
argv = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (argv, g_strdup (PACKAGE_LIBEXECDIR "/sysprof-live-unwinder"));
g_ptr_array_add (argv, g_strdup_printf ("--stack-size=%u", stack_size));
if (-1 == (event_fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (arg_event_fd), &error)))
{
g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
return TRUE;
}
g_ptr_array_add (argv, g_strdup_printf ("--event-fd=%u", next_target_fd));
g_subprocess_launcher_take_fd (launcher, g_steal_fd (&event_fd), next_target_fd++);
g_variant_iter_init (&iter, arg_perf_fds);
while (g_variant_iter_loop (&iter, "(hi)", &perf_fd_handle, &cpu))
{
g_autofd int perf_fd = g_unix_fd_list_get (fd_list, perf_fd_handle, &error);
if (perf_fd < 0)
{
g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
return TRUE;
}
g_ptr_array_add (argv, g_strdup_printf ("--perf-fd=%d:%d", next_target_fd, cpu));
g_subprocess_launcher_take_fd (launcher,
g_steal_fd (&perf_fd),
next_target_fd++);
}
g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL);
if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, pair) < 0)
{
int errsv = errno;
g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation),
G_IO_ERROR,
g_io_error_from_errno (errsv),
g_strerror (errsv));
return TRUE;
}
our_fd = g_steal_fd (&pair[0]);
their_fd = g_steal_fd (&pair[1]);
out_fd_list = g_unix_fd_list_new ();
capture_fd_handle = g_unix_fd_list_append (out_fd_list, their_fd, &error);
if (capture_fd_handle < 0)
{
g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
return TRUE;
}
g_ptr_array_add (argv, g_strdup_printf ("--capture-fd=%d", next_target_fd));
g_subprocess_launcher_take_fd (launcher, g_steal_fd (&our_fd), next_target_fd++);
g_ptr_array_add (argv, NULL);
if (!(subprocess = g_subprocess_launcher_spawnv (launcher, (const char * const *)argv->pdata, &error)))
{
g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
return TRUE;
}
ipc_unwinder_complete_unwind (unwinder,
g_steal_pointer (&invocation),
out_fd_list,
g_variant_new_handle (capture_fd_handle));
g_subprocess_wait_async (subprocess, NULL, NULL, NULL);
return TRUE;
}
static void
unwinder_iface_init (IpcUnwinderIface *iface)
{
iface->handle_unwind = ipc_unwinder_impl_handle_unwind;
}
G_DEFINE_FINAL_TYPE_WITH_CODE (IpcUnwinderImpl, ipc_unwinder_impl, IPC_TYPE_UNWINDER_SKELETON,
G_IMPLEMENT_INTERFACE (IPC_TYPE_UNWINDER, unwinder_iface_init))
static gboolean
ipc_unwinder_impl_g_authorize_method (GDBusInterfaceSkeleton *skeleton,
GDBusMethodInvocation *invocation)
{
PolkitAuthorizationResult *res = NULL;
PolkitAuthority *authority = NULL;
PolkitSubject *subject = NULL;
const gchar *peer_name;
gboolean ret = TRUE;
g_assert (IPC_IS_UNWINDER_IMPL (skeleton));
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
peer_name = g_dbus_method_invocation_get_sender (invocation);
if (!(authority = polkit_authority_get_sync (NULL, NULL)) ||
!(subject = polkit_system_bus_name_new (peer_name)) ||
!(res = polkit_authority_check_authorization_sync (authority,
POLKIT_SUBJECT (subject),
"org.gnome.sysprof3.profile",
NULL,
POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
NULL,
NULL)) ||
!polkit_authorization_result_get_is_authorized (res))
{
g_dbus_method_invocation_return_error (g_steal_pointer (&invocation),
G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Not authorized to make request");
ret = FALSE;
}
g_clear_object (&authority);
g_clear_object (&subject);
g_clear_object (&res);
return ret;
}
static void
ipc_unwinder_impl_finalize (GObject *object)
{
G_OBJECT_CLASS (ipc_unwinder_impl_parent_class)->finalize (object);
}
static void
ipc_unwinder_impl_class_init (IpcUnwinderImplClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass);
object_class->finalize = ipc_unwinder_impl_finalize;
skeleton_class->g_authorize_method = ipc_unwinder_impl_g_authorize_method;
}
static void
ipc_unwinder_impl_init (IpcUnwinderImpl *self)
{
}
IpcUnwinder *
ipc_unwinder_impl_new (void)
{
return g_object_new (IPC_TYPE_UNWINDER_IMPL, NULL);
}

View File

@ -0,0 +1,34 @@
/*
* ipc-unwinder-impl.h
*
* Copyright 2024 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "ipc-unwinder.h"
G_BEGIN_DECLS
#define IPC_TYPE_UNWINDER_IMPL (ipc_unwinder_impl_get_type())
G_DECLARE_FINAL_TYPE (IpcUnwinderImpl, ipc_unwinder_impl, IPC, UNWINDER_IMPL, IpcUnwinderSkeleton)
IpcUnwinder *ipc_unwinder_impl_new (void);
G_END_DECLS

View File

@ -10,18 +10,24 @@ ipc_service_src = gnome.gdbus_codegen('ipc-service',
namespace: 'Ipc',
)
ipc_unwinder_src = gnome.gdbus_codegen('ipc-unwinder',
sources: 'org.gnome.Sysprof3.Unwinder.xml',
interface_prefix: 'org.gnome.Sysprof3.',
namespace: 'Ipc',
)
sysprofd_sources = [
'sysprofd.c',
'ipc-rapl-profiler.c',
'ipc-service-impl.c',
'ipc-unwinder-impl.c',
'sysprof-turbostat.c',
'helpers.c',
ipc_profiler_src,
ipc_service_src,
ipc_unwinder_src,
]
pkglibexecdir = join_paths(get_option('prefix'), get_option('libexecdir'))
sysprofd_deps = [
glib_dep,
gio_dep,

View File

@ -0,0 +1,23 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.gnome.Sysprof3.Unwinder">
<!--
Unwind:
@stack_size: the size of stacks that are sampled
@perf_fds: an array of (perf_fd, CPU number)
@event_fd: an event fd to write to for notifying unwinder should exit
@capture_fd: (out): a FD that will be written to containing samples
Unwinding will stop when capture_fd can no longer be written to
such as being closed by the consumer of this API.
-->
<method name="Unwind">
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
<arg type="u" name="stack_size" direction="in"/>
<arg type="a(hi)" name="perf_fds" direction="in"/>
<arg type="h" name="event_fd" direction="in"/>
<arg type="h" name="capture_fd" direction="out"/>
</method>
</interface>
</node>

View File

@ -30,9 +30,11 @@
#include "ipc-rapl-profiler.h"
#include "ipc-service-impl.h"
#include "ipc-unwinder-impl.h"
#define V3_PATH "/org/gnome/Sysprof3"
#define RAPL_PATH "/org/gnome/Sysprof3/RAPL"
#define UNWINDER_PATH "/org/gnome/Sysprof3/Unwinder"
#define NAME_ACQUIRE_DELAY_SECS 3
#define INACTIVITY_TIMEOUT_SECS 120
@ -126,6 +128,7 @@ main (gint argc,
{
g_autoptr(IpcProfiler) rapl = ipc_rapl_profiler_new ();
g_autoptr(IpcService) v3_service = ipc_service_impl_new ();
g_autoptr(IpcUnwinder) unwinder = ipc_unwinder_impl_new ();
g_signal_connect (v3_service, "activity", G_CALLBACK (activity_cb), NULL);
g_signal_connect (rapl, "activity", G_CALLBACK (activity_cb), NULL);
@ -133,7 +136,8 @@ main (gint argc,
activity_cb (NULL, NULL);
if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v3_service), bus, V3_PATH, &error) &&
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (rapl), bus, RAPL_PATH, &error))
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (rapl), bus, RAPL_PATH, &error) &&
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (unwinder), bus, UNWINDER_PATH, &error))
{
for (guint i = 0; i < G_N_ELEMENTS (bus_names); i++)
{