diff --git a/src/libsysprof-capture/sysprof-capture-cursor.c b/src/libsysprof-capture/sysprof-capture-cursor.c index 7697ad48..376e8603 100644 --- a/src/libsysprof-capture/sysprof-capture-cursor.c +++ b/src/libsysprof-capture/sysprof-capture-cursor.c @@ -175,6 +175,10 @@ sysprof_capture_cursor_foreach (SysprofCaptureCursor *self, delegate = READ_DELEGATE (sysprof_capture_reader_read_sample); break; + case SYSPROF_CAPTURE_FRAME_TRACE: + delegate = READ_DELEGATE (sysprof_capture_reader_read_trace); + break; + case SYSPROF_CAPTURE_FRAME_LOG: delegate = READ_DELEGATE (sysprof_capture_reader_read_log); break; diff --git a/src/libsysprof-capture/sysprof-capture-reader.c b/src/libsysprof-capture/sysprof-capture-reader.c index 6b7fa051..6227af9b 100644 --- a/src/libsysprof-capture/sysprof-capture-reader.c +++ b/src/libsysprof-capture/sysprof-capture-reader.c @@ -925,6 +925,52 @@ sysprof_capture_reader_read_sample (SysprofCaptureReader *self) return sample; } +const SysprofCaptureTrace * +sysprof_capture_reader_read_trace (SysprofCaptureReader *self) +{ + SysprofCaptureTrace *trace; + + assert (self != NULL); + assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0); + assert (self->pos <= self->bufsz); + + if (!sysprof_capture_reader_ensure_space_for (self, sizeof *trace)) + return NULL; + + trace = (SysprofCaptureTrace *)(void *)&self->buf[self->pos]; + + sysprof_capture_reader_bswap_frame (self, &trace->frame); + + if (trace->frame.type != SYSPROF_CAPTURE_FRAME_TRACE) + return NULL; + + if (trace->frame.len < sizeof *trace) + return NULL; + + if (self->endian != __BYTE_ORDER) + trace->n_addrs = bswap_16 (trace->n_addrs); + + if (trace->frame.len < (sizeof *trace + (sizeof(SysprofCaptureAddress) * trace->n_addrs))) + return NULL; + + if (!sysprof_capture_reader_ensure_space_for (self, trace->frame.len)) + return NULL; + + trace = (SysprofCaptureTrace *)(void *)&self->buf[self->pos]; + + if (SYSPROF_UNLIKELY (self->endian != __BYTE_ORDER)) + { + unsigned int i; + + for (i = 0; i < trace->n_addrs; i++) + trace->addrs[i] = bswap_64 (trace->addrs[i]); + } + + self->pos += trace->frame.len; + + return trace; +} + const SysprofCaptureCounterDefine * sysprof_capture_reader_read_counter_define (SysprofCaptureReader *self) { diff --git a/src/libsysprof-capture/sysprof-capture-reader.h b/src/libsysprof-capture/sysprof-capture-reader.h index 9d10d700..fbf6f143 100644 --- a/src/libsysprof-capture/sysprof-capture-reader.h +++ b/src/libsysprof-capture/sysprof-capture-reader.h @@ -111,6 +111,8 @@ const SysprofCaptureProcess *sysprof_capture_reader_read_process ( SYSPROF_AVAILABLE_IN_ALL const SysprofCaptureSample *sysprof_capture_reader_read_sample (SysprofCaptureReader *self); SYSPROF_AVAILABLE_IN_ALL +const SysprofCaptureTrace *sysprof_capture_reader_read_trace (SysprofCaptureReader *self); +SYSPROF_AVAILABLE_IN_ALL const SysprofCaptureJitmap *sysprof_capture_reader_read_jitmap (SysprofCaptureReader *self); SYSPROF_AVAILABLE_IN_ALL const SysprofCaptureCounterDefine *sysprof_capture_reader_read_counter_define (SysprofCaptureReader *self); diff --git a/src/libsysprof-capture/sysprof-capture-types.h b/src/libsysprof-capture/sysprof-capture-types.h index 20295a2e..9be4a0eb 100644 --- a/src/libsysprof-capture/sysprof-capture-types.h +++ b/src/libsysprof-capture/sysprof-capture-types.h @@ -140,10 +140,11 @@ typedef enum SYSPROF_CAPTURE_FRAME_FILE_CHUNK = 13, SYSPROF_CAPTURE_FRAME_ALLOCATION = 14, SYSPROF_CAPTURE_FRAME_OVERLAY = 15, + SYSPROF_CAPTURE_FRAME_TRACE = 16, } SysprofCaptureFrameType; /* Not part of ABI */ -#define SYSPROF_CAPTURE_FRAME_LAST 16 +#define SYSPROF_CAPTURE_FRAME_LAST 17 SYSPROF_ALIGNED_BEGIN(1) typedef struct @@ -225,6 +226,18 @@ typedef struct } SysprofCaptureSample SYSPROF_ALIGNED_END(1); +SYSPROF_ALIGNED_BEGIN(1) +typedef struct +{ + SysprofCaptureFrame frame; + uint32_t n_addrs : 16; + uint32_t entering : 1; + uint32_t padding1 : 15; + int32_t tid; + SysprofCaptureAddress addrs[0]; +} SysprofCaptureTrace +SYSPROF_ALIGNED_END(1); + SYSPROF_ALIGNED_BEGIN(1) typedef struct { @@ -357,6 +370,7 @@ SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureMap) == 56, "SysprofCaptureMap chan SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureJitmap) == 28, "SysprofCaptureJitmap changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureProcess) == 24, "SysprofCaptureProcess changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureSample) == 32, "SysprofCaptureSample changed size"); +SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureTrace) == 32, "SysprofCaptureTrace changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureFork) == 28, "SysprofCaptureFork changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureExit) == 24, "SysprofCaptureExit changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureTimestamp) == 24, "SysprofCaptureTimestamp changed size"); @@ -373,6 +387,7 @@ SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureOverlay) == 32, "SysprofCaptureOver SYSPROF_STATIC_ASSERT ((offsetof (SysprofCaptureAllocation, addrs) % SYSPROF_CAPTURE_ALIGN) == 0, "SysprofCaptureAllocation.addrs is not aligned"); SYSPROF_STATIC_ASSERT ((offsetof (SysprofCaptureSample, addrs) % SYSPROF_CAPTURE_ALIGN) == 0, "SysprofCaptureSample.addrs is not aligned"); +SYSPROF_STATIC_ASSERT ((offsetof (SysprofCaptureTrace, addrs) % SYSPROF_CAPTURE_ALIGN) == 0, "SysprofCaptureTrace.addrs is not aligned"); static inline int sysprof_capture_address_compare (SysprofCaptureAddress a, diff --git a/src/libsysprof-capture/sysprof-capture-writer-cat.c b/src/libsysprof-capture/sysprof-capture-writer-cat.c index 35de0620..e8de2367 100644 --- a/src/libsysprof-capture/sysprof-capture-writer-cat.c +++ b/src/libsysprof-capture/sysprof-capture-writer-cat.c @@ -416,6 +416,32 @@ sysprof_capture_writer_cat (SysprofCaptureWriter *self, break; } + case SYSPROF_CAPTURE_FRAME_TRACE: + { + const SysprofCaptureTrace *frame; + + if (!(frame = sysprof_capture_reader_read_trace (reader))) + goto panic; + + { + SysprofCaptureAddress addrs[frame->n_addrs]; + + for (unsigned int z = 0; z < frame->n_addrs; z++) + addrs[z] = translate_table_translate (tables, TRANSLATE_ADDR, frame->addrs[z]); + + sysprof_capture_writer_add_trace (self, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + frame->tid, + addrs, + frame->n_addrs, + frame->entering); + } + + break; + } + case SYSPROF_CAPTURE_FRAME_CTRDEF: { const SysprofCaptureCounterDefine *frame; diff --git a/src/libsysprof-capture/sysprof-capture-writer.c b/src/libsysprof-capture/sysprof-capture-writer.c index d05981c2..1e3f57e4 100644 --- a/src/libsysprof-capture/sysprof-capture-writer.c +++ b/src/libsysprof-capture/sysprof-capture-writer.c @@ -798,6 +798,42 @@ sysprof_capture_writer_add_sample (SysprofCaptureWriter *self, return true; } +bool +sysprof_capture_writer_add_trace (SysprofCaptureWriter *self, + int64_t time, + int cpu, + int32_t pid, + int32_t tid, + const SysprofCaptureAddress *addrs, + unsigned int n_addrs, + bool entering) +{ + SysprofCaptureTrace *ev; + size_t len; + + assert (self != NULL); + + len = sizeof *ev + (n_addrs * sizeof (SysprofCaptureAddress)); + + ev = (SysprofCaptureTrace *)sysprof_capture_writer_allocate (self, &len); + if (!ev) + return false; + + sysprof_capture_writer_frame_init (&ev->frame, + len, + cpu, + pid, + time, + SYSPROF_CAPTURE_FRAME_SAMPLE); + ev->n_addrs = n_addrs; + ev->tid = tid; + ev->entering = !!entering; + + memcpy (ev->addrs, addrs, (n_addrs * sizeof (SysprofCaptureAddress))); + + return true; +} + bool sysprof_capture_writer_add_fork (SysprofCaptureWriter *self, int64_t time, @@ -1659,7 +1695,7 @@ _sysprof_capture_writer_add_raw (SysprofCaptureWriter *self, return false; assert (fr->len == len); - assert (fr->type < 16); + assert (fr->type < SYSPROF_CAPTURE_FRAME_LAST); memcpy (begin, fr, fr->len); diff --git a/src/libsysprof-capture/sysprof-capture-writer.h b/src/libsysprof-capture/sysprof-capture-writer.h index 4893c226..6cbbd5ee 100644 --- a/src/libsysprof-capture/sysprof-capture-writer.h +++ b/src/libsysprof-capture/sysprof-capture-writer.h @@ -143,6 +143,15 @@ bool sysprof_capture_writer_add_sample (Sy const SysprofCaptureAddress *addrs, unsigned int n_addrs); SYSPROF_AVAILABLE_IN_ALL +bool sysprof_capture_writer_add_trace (SysprofCaptureWriter *self, + int64_t time, + int cpu, + int32_t pid, + int32_t tid, + const SysprofCaptureAddress *addrs, + unsigned int n_addrs, + bool entering); +SYSPROF_AVAILABLE_IN_ALL bool sysprof_capture_writer_add_fork (SysprofCaptureWriter *self, int64_t time, int cpu, diff --git a/src/libsysprof-capture/sysprof-collector.c b/src/libsysprof-capture/sysprof-collector.c index 803aa02e..c8fad1b8 100644 --- a/src/libsysprof-capture/sysprof-collector.c +++ b/src/libsysprof-capture/sysprof-collector.c @@ -576,6 +576,43 @@ sysprof_collector_sample (SysprofBacktraceFunc backtrace_func, } COLLECTOR_END; } +void +sysprof_collector_trace (SysprofBacktraceFunc backtrace_func, + void *backtrace_data, + bool entering) +{ + COLLECTOR_BEGIN { + SysprofCaptureTrace *ev; + size_t len; + + len = sizeof *ev + (sizeof (SysprofCaptureTrace) * MAX_UNWIND_DEPTH); + + if ((ev = mapped_ring_buffer_allocate (collector->buffer, len))) + { + int n_addrs; + + /* See comment from sysprof_collector_allocate(). */ + if (backtrace_func) + n_addrs = backtrace_func (ev->addrs, MAX_UNWIND_DEPTH, backtrace_data); + else + n_addrs = 0; + + ev->n_addrs = ((n_addrs < 0) ? 0 : (n_addrs > MAX_UNWIND_DEPTH) ? MAX_UNWIND_DEPTH : n_addrs); + ev->frame.len = sizeof *ev + sizeof (SysprofCaptureAddress) * ev->n_addrs; + ev->frame.type = SYSPROF_CAPTURE_FRAME_TRACE; + ev->frame.cpu = _do_getcpu (); + ev->frame.pid = collector->pid; + ev->frame.time = SYSPROF_CAPTURE_CURRENT_TIME; + ev->tid = collector->tid; + ev->entering = !!entering; + ev->padding1 = 0; + + mapped_ring_buffer_advance (collector->buffer, ev->frame.len); + } + + } COLLECTOR_END; +} + void sysprof_collector_mark (int64_t time, int64_t duration, diff --git a/src/libsysprof-capture/sysprof-collector.h b/src/libsysprof-capture/sysprof-collector.h index 6f13a805..c53dc984 100644 --- a/src/libsysprof-capture/sysprof-collector.h +++ b/src/libsysprof-capture/sysprof-collector.h @@ -75,6 +75,10 @@ void sysprof_collector_allocate (SysprofCaptureAddress SYSPROF_AVAILABLE_IN_3_36 void sysprof_collector_sample (SysprofBacktraceFunc backtrace_func, void *backtrace_data); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_collector_trace (SysprofBacktraceFunc backtrace_func, + void *backtrace_data, + bool entering); SYSPROF_AVAILABLE_IN_3_36 void sysprof_collector_mark (int64_t time, int64_t duration, diff --git a/src/libsysprof-profile/meson.build b/src/libsysprof-profile/meson.build index 9f7cbec4..33537699 100644 --- a/src/libsysprof-profile/meson.build +++ b/src/libsysprof-profile/meson.build @@ -13,6 +13,7 @@ libsysprof_profile_public_sources = [ 'sysprof-recording.c', 'sysprof-sampler.c', 'sysprof-spawnable.c', + 'sysprof-tracer.c', ] libsysprof_profile_private_sources = [ @@ -41,6 +42,7 @@ libsysprof_profile_public_headers = [ 'sysprof-recording.h', 'sysprof-sampler.h', 'sysprof-spawnable.h', + 'sysprof-tracer.h', ] if host_machine.system() == 'linux' diff --git a/src/libsysprof-profile/sysprof-profile.h b/src/libsysprof-profile/sysprof-profile.h index 9bf03fcb..d3e9afee 100644 --- a/src/libsysprof-profile/sysprof-profile.h +++ b/src/libsysprof-profile/sysprof-profile.h @@ -39,6 +39,7 @@ G_BEGIN_DECLS # include "sysprof-recording.h" # include "sysprof-sampler.h" # include "sysprof-spawnable.h" +# include "sysprof-tracer.h" #undef SYSPROF_PROFILE_INSIDE G_END_DECLS diff --git a/src/libsysprof-profile/sysprof-tracer.c b/src/libsysprof-profile/sysprof-tracer.c new file mode 100644 index 00000000..8943d0b8 --- /dev/null +++ b/src/libsysprof-profile/sysprof-tracer.c @@ -0,0 +1,88 @@ +/* sysprof-tracer.c + * + * Copyright 2023 Christian Hergert + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" +#include "sysprof-tracer.h" + +/** + * SysprofTracer: + * + * The `SysprofTracer` instrument provides integration with GCC's + * `-finstrument-functions`. It uses an `LD_PRELOAD` on Linux to record + * stacktraces on `__cyg_profile_func_enter` and `__cyg_profile_func_exit`. + * + * On macOS, `profile_func_enter` and `profile_func_exit` are used. + */ + +struct _SysprofTracer +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofTracerClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofTracer, sysprof_tracer, SYSPROF_TYPE_INSTRUMENT) + +static DexFuture * +sysprof_tracer_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + SysprofSpawnable *spawnable; + + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + if ((spawnable = _sysprof_recording_get_spawnable (recording))) + sysprof_spawnable_add_ld_preload (spawnable, + PACKAGE_LIBDIR"/libsysprof-tracer-" API_VERSION_S ".so"); + else + _sysprof_recording_diagnostic (recording, + _("Tracer"), + _("Tracing requires spawning a program compiled with ‘-finstrument-functions’. Tracing will not be available.")); + + return dex_future_new_for_boolean (TRUE); +} + +static void +sysprof_tracer_class_init (SysprofTracerClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->prepare = sysprof_tracer_prepare; +} + +static void +sysprof_tracer_init (SysprofTracer *self) +{ +} + +SysprofInstrument * +sysprof_tracer_new (void) +{ + return g_object_new (SYSPROF_TYPE_TRACER, NULL); +} diff --git a/src/libsysprof-profile/sysprof-tracer.h b/src/libsysprof-profile/sysprof-tracer.h new file mode 100644 index 00000000..f7966fbd --- /dev/null +++ b/src/libsysprof-profile/sysprof-tracer.h @@ -0,0 +1,42 @@ +/* sysprof-tracer.h + * + * Copyright 2023 Christian Hergert + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TRACER (sysprof_tracer_get_type()) +#define SYSPROF_IS_TRACER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_TRACER) +#define SYSPROF_TRACER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_TRACER, SysprofTracer) +#define SYSPROF_TRACER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_TRACER, SysprofTracerClass) + +typedef struct _SysprofTracer SysprofTracer; +typedef struct _SysprofTracerClass SysprofTracerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_tracer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_tracer_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofTracer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof-profile/tests/test-profiler.c b/src/libsysprof-profile/tests/test-profiler.c index af2800f5..6c99b7d1 100644 --- a/src/libsysprof-profile/tests/test-profiler.c +++ b/src/libsysprof-profile/tests/test-profiler.c @@ -29,9 +29,11 @@ static GMainLoop *main_loop; static char *capture_file; static SysprofRecording *active_recording; static gboolean memprof; +static gboolean tracer; static const GOptionEntry entries[] = { { "capture", 'c', 0, G_OPTION_ARG_FILENAME, &capture_file, "The file to capture into", "CAPTURE" }, { "memprof", 'm', 0, G_OPTION_ARG_NONE, &memprof, "Do memory allocation tracking on subprocess" }, + { "tracer", 't', 0, G_OPTION_ARG_NONE, &tracer, "Enable tracing with __cyg_profile_enter" }, { 0 } }; @@ -172,11 +174,15 @@ main (int argc, sysprof_profiler_add_instrument (profiler, sysprof_energy_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); - sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); if (memprof) sysprof_profiler_add_instrument (profiler, sysprof_malloc_tracing_new ()); + if (tracer) + sysprof_profiler_add_instrument (profiler, sysprof_tracer_new ()); + else + sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); + for (int i = 1; i < argc; i++) { if (strcmp (argv[i], "--") == 0 && i+1 < argc) diff --git a/src/preload/meson.build b/src/preload/meson.build index a1a6e603..9c39af50 100644 --- a/src/preload/meson.build +++ b/src/preload/meson.build @@ -24,3 +24,10 @@ libsysprof_speedtrack_preload = shared_library('sysprof-speedtrack-@0@'.format(l install: true, install_dir: get_option('libdir'), ) + +libsysprof_tracer_preload = shared_library('sysprof-tracer-@0@'.format(libsysprof_api_version), + ['sysprof-tracer-collector.c'], + dependencies: preload_deps, + install: true, + install_dir: get_option('libdir'), +) diff --git a/src/preload/sysprof-tracer.c b/src/preload/sysprof-tracer-collector.c similarity index 71% rename from src/preload/sysprof-tracer.c rename to src/preload/sysprof-tracer-collector.c index 95937b0f..c427c5ad 100644 --- a/src/preload/sysprof-tracer.c +++ b/src/preload/sysprof-tracer-collector.c @@ -28,6 +28,15 @@ #include "gconstructor.h" +#ifdef __GNUC__ +# define GNUC_CHECK_VERSION(major, minor) \ + ((__GNUC__ > (major)) || \ + ((__GNUC__ == (major)) && \ + (__GNUC_MINOR__ >= (minor)))) +#else +# define GNUC_CHECK_VERSION(major, minor) 0 +#endif + #if defined (G_HAS_CONSTRUCTORS) # ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA # pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(collector_init_ctor) @@ -49,24 +58,30 @@ collector_init_ctor (void) sysprof_collector_init (); } -/* TODO: - * - * This is just an example. - * +/* * What we would really want to do is to have a new frame type for enter/exit * tracing so that we can only push/pop the new address to the sample. Then - * when decoding it can recreate stack traces if necessary. + * when decoding it can recreate stack traces if necessary. But for now, we + * can emulate that by just adding a sample when we enter a function and + * leave a function. The rest could be done in post-processing. */ +#if GNUC_CHECK_VERSION(3,0) +__attribute__((no_instrument_function)) +#endif void profile_func_enter (void *func, void *call_site) { - sysprof_collector_sample (backtrace_func, NULL); + sysprof_collector_trace (backtrace_func, NULL, TRUE); } +#if GNUC_CHECK_VERSION(3,0) +__attribute__((no_instrument_function)) +#endif void profile_func_exit (void *func, void *call_site) { + sysprof_collector_trace (backtrace_func, NULL, FALSE); } diff --git a/src/tools/sysprof-dump.c b/src/tools/sysprof-dump.c index 5c12c4b7..1019282a 100644 --- a/src/tools/sysprof-dump.c +++ b/src/tools/sysprof-dump.c @@ -301,6 +301,26 @@ main (gint argc, break; } + case SYSPROF_CAPTURE_FRAME_TRACE: + { + const SysprofCaptureTrace *s = sysprof_capture_reader_read_trace (reader); + gdouble ptime = (s->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; + SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; + + g_print ("TRACE: pid=%d tid=%d time=%" G_GINT64_FORMAT " (%lf) %s\n", + s->frame.pid, s->tid, s->frame.time, ptime, + s->entering ? "ENTER" : "EXIT"); + + for (guint i = 0; i < s->n_addrs; i++) + { + g_autofree char *name = symbolize (resolvers, &s->frame, &context, s->addrs[i]); + + g_print (" " SYSPROF_CAPTURE_ADDRESS_FORMAT " (%s)\n", s->addrs[i], name); + } + + break; + } + case SYSPROF_CAPTURE_FRAME_TIMESTAMP: { const SysprofCaptureTimestamp *ts = sysprof_capture_reader_read_timestamp (reader);