From 402c41dcd8a85c1dee5defa81a820bf03f14ee7a Mon Sep 17 00:00:00 2001 From: Justin Michaud Date: Tue, 22 Apr 2025 12:54:28 -0600 Subject: [PATCH 1/5] Add Perf JITDump support --- src/libsysprof/meson.build | 2 + src/libsysprof/sysprof-document-loader.c | 2 + src/libsysprof/sysprof-jitdump-symbolizer.c | 491 ++++++++++++++++++++ src/libsysprof/sysprof-jitdump-symbolizer.h | 51 ++ src/libsysprof/sysprof.h | 1 + src/sysprof/sysprof-recording-template.c | 1 + 6 files changed, 548 insertions(+) create mode 100644 src/libsysprof/sysprof-jitdump-symbolizer.c create mode 100644 src/libsysprof/sysprof-jitdump-symbolizer.h diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index 7c823021..6ad10716 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -39,6 +39,7 @@ libsysprof_public_sources = [ 'sysprof-elf-symbolizer.c', 'sysprof-energy-usage.c', 'sysprof-instrument.c', + 'sysprof-jitdump-symbolizer.c', 'sysprof-jitmap-symbolizer.c', 'sysprof-kallsyms-symbolizer.c', 'sysprof-malloc-tracing.c', @@ -106,6 +107,7 @@ libsysprof_public_headers = [ 'sysprof-elf-symbolizer.h', 'sysprof-energy-usage.h', 'sysprof-instrument.h', + 'sysprof-jitdump-symbolizer.h', 'sysprof-jitmap-symbolizer.h', 'sysprof-kallsyms-symbolizer.h', 'sysprof-malloc-tracing.h', diff --git a/src/libsysprof/sysprof-document-loader.c b/src/libsysprof/sysprof-document-loader.c index 60266939..dcc0a97c 100644 --- a/src/libsysprof/sysprof-document-loader.c +++ b/src/libsysprof/sysprof-document-loader.c @@ -32,6 +32,7 @@ #include "sysprof-document-loader-private.h" #include "sysprof-document-private.h" #include "sysprof-elf-symbolizer.h" +#include "sysprof-jitdump-symbolizer.h" #include "sysprof-jitmap-symbolizer.h" #include "sysprof-kallsyms-symbolizer.h" #include "sysprof-multi-symbolizer.h" @@ -207,6 +208,7 @@ set_default_symbolizer (SysprofDocumentLoader *self) sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new ()); sysprof_multi_symbolizer_take (multi, sysprof_elf_symbolizer_new ()); sysprof_multi_symbolizer_take (multi, sysprof_jitmap_symbolizer_new ()); + sysprof_multi_symbolizer_take (multi, sysprof_jitdump_symbolizer_new ()); #if HAVE_DEBUGINFOD { diff --git a/src/libsysprof/sysprof-jitdump-symbolizer.c b/src/libsysprof/sysprof-jitdump-symbolizer.c new file mode 100644 index 00000000..e92c7a70 --- /dev/null +++ b/src/libsysprof/sysprof-jitdump-symbolizer.c @@ -0,0 +1,491 @@ +/* sysprof-jitdump-symbolizer.c + * + * Copyright 2025 Justin Michaud + * + * 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 "timsort/gtktimsortprivate.h" + +#include "sysprof-document-private.h" +#include "sysprof-jitdump-symbolizer.h" +#include "sysprof-symbol-private.h" +#include "sysprof-symbolizer-private.h" + +typedef struct _Jitdump +{ + SysprofAddress address; + GRefString *name; + gsize code_size; +} Jitdump; + +struct _SysprofJitdumpSymbolizer +{ + SysprofSymbolizer parent_instance; + GArray *jitdumps; +}; + +struct _SysprofJitdumpSymbolizerClass +{ + SysprofSymbolizerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofJitdumpSymbolizer, sysprof_jitdump_symbolizer, SYSPROF_TYPE_SYMBOLIZER) + +typedef struct +{ + SysprofDocument *document; +} Prepare; + +static void +prepare_free (gpointer data) +{ + Prepare *prepare = data; + + g_clear_object (&prepare->document); + g_free (prepare); +} + +static void +jitdump_clear (gpointer data) +{ + Jitdump *j = data; + g_clear_pointer (&j->name, g_ref_string_release); +} + +static int +compare_by_address (gconstpointer a, + gconstpointer b) +{ + const Jitdump *jitdump_a = a; + const Jitdump *jitdump_b = b; + + if (jitdump_a->address < jitdump_b->address) + return -1; + else if (jitdump_a->address > jitdump_b->address) + return 1; + else + return 0; +} + +static void +sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, + Prepare *prepare, + GDataInputStream *input, + GCancellable *cancellable, + GError **error) +{ + const guint32 JIT_CODE_LOAD = 0; + Jitdump map; + + g_assert (G_IS_DATA_INPUT_STREAM (input)); + g_data_input_stream_set_byte_order (input, G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN); + + // https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/jitdump-specification.txt + // Each jitdump file starts with a fixed size header containing the following fields in order: + // * uint32_t magic : a magic number tagging the file type. The value is 4-byte long and represents the string "JiTD" in ASCII form. It written is as 0x4A695444. The reader will detect an endian mismatch when it reads 0x4454694a. + // * uint32_t version : a 4-byte value representing the format version. It is currently set to 1 + // * uint32_t total_size: size in bytes of file header + // * uint32_t elf_mach : ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h) + // * uint32_t pad1 : padding. Reserved for future use + // * uint32_t pid : JIT runtime process identification (OS specific) + // * uint64_t timestamp : timestamp of when the file was created + // * uint64_t flags : a bitmask of flags + { + guint32 magic; + guint32 version; + guint32 total_size; + + magic = g_data_input_stream_read_uint32 (input, cancellable, error); + if (*error != NULL) + return; + + version = g_data_input_stream_read_uint32 (input, cancellable, error); + if (*error != NULL) + return; + + total_size = g_data_input_stream_read_uint32 (input, cancellable, error); + if (*error != NULL) + return; + + g_input_stream_skip (G_INPUT_STREAM (input), total_size - sizeof (magic) - sizeof (version) - sizeof (total_size), cancellable, error); + + if (magic != 0x4A695444) + { + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid magic number: %d (expect 0x4454694a for endianness mismatch)", magic); + return; + } + + if (version != 1) + { + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid version"); + return; + } + } + + // The file header is immediately followed by records. Each record starts with a fixed size header describing the record that follows. + // The record header is specified in order as follows: + // * uint32_t id : a value identifying the record type (see below) + // * uint32_t total_size: the size in bytes of the record including the header. + // * uint64_t timestamp : a timestamp of when the record was created. + + while (g_buffered_input_stream_get_available (G_BUFFERED_INPUT_STREAM (input)) != 0) + { + guint32 id; + guint32 total_size; + guint64 timestamp; + + if (*error != NULL) + return; + + id = g_data_input_stream_read_uint32 (input, cancellable, error); + if (*error != NULL) + return; + + total_size = g_data_input_stream_read_uint32 (input, cancellable, error); + if (*error != NULL) + return; + + if (total_size < 2 * sizeof (uint32_t) + sizeof (uint64_t)) + { + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid record size"); + return; + } + + timestamp = g_data_input_stream_read_uint64 (input, cancellable, error); + if (*error != NULL) + return; + + if (id == JIT_CODE_LOAD) + { + // The record has the following fields following the fixed-size record header in order: + // * uint32_t pid: OS process id of the runtime generating the jitted code + // * uint32_t tid: OS thread identification of the runtime thread generating the jitted code + // * uint64_t vma: virtual address of jitted code start + // * uint64_t code_addr: code start address for the jitted code. By default vma = code_addr + // * uint64_t code_size: size in bytes of the generated jitted code + // * uint64_t code_index: unique identifier for the jitted code (see below) + // * char[n]: function name in ASCII including the null termination + // * native code: raw byte encoding of the jitted code + guint32 pid; + guint32 tid; + guint64 vma; + guint64 code_addr; + guint64 code_size; + guint64 code_index; + + gsize name_len; + g_autofree gchar *name = NULL; + g_autofree guint8 *code = NULL; + gsize code_read_bytes = 0; + gsize name_read_bytes = 0; + + if (total_size < 2 * sizeof (uint32_t) + 4 * sizeof (uint64_t)) + { + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid jit load record size"); + return; + } + + pid = g_data_input_stream_read_uint32 (input, cancellable, error); + if (*error != NULL) + return; + + tid = g_data_input_stream_read_uint32 (input, cancellable, error); + if (*error != NULL) + return; + + vma = g_data_input_stream_read_uint64 (input, cancellable, error); + if (*error != NULL) + return; + + code_addr = g_data_input_stream_read_uint64 (input, cancellable, error); + if (*error != NULL) + return; + + code_size = g_data_input_stream_read_uint64 (input, cancellable, error); + if (*error != NULL) + return; + + code_index = g_data_input_stream_read_uint64 (input, cancellable, error); + if (*error != NULL) + return; + + // Includes null byte + name_len = total_size - code_size - (2 * sizeof (uint32_t) + 4 * sizeof (uint64_t)) - (2 * sizeof (uint32_t) + sizeof (uint64_t)); + name = g_malloc0(name_len); + code = g_malloc0(code_size); + g_input_stream_read_all (G_INPUT_STREAM (input), name, name_len, &name_read_bytes, cancellable, error); + if (*error != NULL) + return; + + if (name_read_bytes != name_len) + { + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record name. Read %lu bytes, expected %lu", name_read_bytes, name_len); + return; + } + + g_input_stream_read_all (G_INPUT_STREAM (input), code, code_size, &code_read_bytes, cancellable, error); + if (*error != NULL) + return; + + if (code_read_bytes != code_size) + { + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code. Read %lu bytes, expected %lu", code_read_bytes, code_size); + return; + } + + if (vma != code_addr) + { + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Unexpected vma: %lu code_addr:%lu", vma, code_addr); + return; + } + + (void) pid; + (void) tid; + (void) timestamp; + (void) code_index; + map.name = _sysprof_document_ref_string (prepare->document, name); + map.address = code_addr; + map.code_size = code_size; + g_array_append_val (self->jitdumps, map); + } + else + { + // Skip this record. + gsize skip = total_size - (2 * sizeof (uint32_t) + sizeof (uint64_t)); + gsize skipped = g_input_stream_skip (G_INPUT_STREAM (input), skip, cancellable, error); + g_assert_not_reached (); + if (skipped != skip) + { + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Failed to skip record: %ld %ld", skip, skipped); + return; + } + + if (*error != NULL) + return; + } + g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (input), -1, cancellable, error); + if (*error != NULL) + return; + } + + if (*error != NULL) + return; +} + +static void +sysprof_jitdump_symbolizer_prepare_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SysprofJitdumpSymbolizer *self = source_object; + Prepare *prepare = task_data; + g_autoptr(GListModel) processes = NULL; + guint n_processes; + + g_assert (G_IS_TASK (task)); + g_assert (SYSPROF_IS_JITDUMP_SYMBOLIZER (self)); + g_assert (prepare != NULL); + g_assert (SYSPROF_IS_DOCUMENT (prepare->document)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + processes = sysprof_document_list_processes (prepare->document); + n_processes = g_list_model_get_n_items (processes); + + for (guint i = 0; i < n_processes; i++) + { + g_autoptr(SysprofDocumentProcess) process = g_list_model_get_item (processes, i); + g_autoptr(GListModel) memory_maps = sysprof_document_process_list_memory_maps (process); + guint n_memory_maps = g_list_model_get_n_items (memory_maps); + + for (guint j = 0; j < n_memory_maps; j++) + { + g_autoptr(SysprofDocumentMmap) memory_map = g_list_model_get_item (memory_maps, j); + const char *file_path = sysprof_document_mmap_get_file (memory_map); + g_autoptr(GFile) file_handle = NULL; + g_autofree char *file_basename = NULL; + g_autoptr(GFileInputStream) stream = NULL; + g_autoptr(GError) error = NULL; + + if (file_path == NULL) + continue; + + file_handle = g_file_new_for_path (file_path); + file_basename = g_file_get_basename (file_handle); + + if (file_basename == NULL) + continue; + + if (!g_str_has_prefix (file_basename, "jit-")) + continue; + + if (!g_str_has_suffix (file_basename, ".dump")) + continue; + + stream = g_file_read (file_handle, cancellable, &error); + if (stream == NULL) + { + g_warning ("Found reference to Perf JITDump file %s, but file does not exist.", file_path); + continue; + } + + { + g_autoptr(GDataInputStream) data_stream = g_data_input_stream_new (G_INPUT_STREAM (stream)); + sysprof_jitdump_symbolizer_load_jitdump (self, prepare, data_stream, cancellable, &error); + } + + if (error != NULL) + { + g_warning ("Failed to load Perf JITDump file %s: %s", file_path, error->message); + g_clear_error (&error); + continue; + } + } + } + + gtk_tim_sort (self->jitdumps->data, + self->jitdumps->len, + sizeof (Jitdump), + (GCompareDataFunc)compare_by_address, + NULL); + + // for (int i = 0; i < self->jitdumps->len; ++i) + // { + // Jitdump *jitdump = &g_array_index (self->jitdumps, Jitdump, i); + // g_warning ("Got dump: %s with address %lx", jitdump->name, jitdump->address); + // } + + g_task_return_boolean (task, TRUE); +} + +static void +sysprof_jitdump_symbolizer_prepare_async (SysprofSymbolizer *symbolizer, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SysprofJitdumpSymbolizer *self = (SysprofJitdumpSymbolizer *)symbolizer; + g_autoptr(GTask) task = NULL; + Prepare *prepare; + + g_assert (SYSPROF_IS_JITDUMP_SYMBOLIZER (self)); + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + prepare = g_new0 (Prepare, 1); + prepare->document = g_object_ref (document); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_jitdump_symbolizer_prepare_async); + g_task_set_task_data (task, prepare, prepare_free); + g_task_run_in_thread (task, sysprof_jitdump_symbolizer_prepare_worker); +} + +static gboolean +sysprof_jitdump_symbolizer_prepare_finish (SysprofSymbolizer *symbolizer, + GAsyncResult *result, + GError **error) +{ + g_assert (SYSPROF_IS_JITDUMP_SYMBOLIZER (symbolizer)); + g_assert (G_IS_TASK (result)); + g_assert (g_task_is_valid (result, symbolizer)); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static SysprofSymbol * +sysprof_jitdump_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + SysprofJitdumpSymbolizer *self = (SysprofJitdumpSymbolizer *)symbolizer; + // const Jitdump key = { address, NULL }; + const Jitdump *match = NULL; + + if (context != SYSPROF_ADDRESS_CONTEXT_NONE && + context != SYSPROF_ADDRESS_CONTEXT_USER) + return NULL; + + // TODO bsearch + for (int i = 0; i < self->jitdumps->len; ++i) + { + const Jitdump *jitdump = &g_array_index(self->jitdumps, Jitdump, i); + if (jitdump->address <= address && jitdump->address + jitdump->code_size > address) + { + match = jitdump; + break; + } + } + // match = bsearch (&key, + // self->jitdumps->data, + // self->jitdumps->len, + // sizeof (Jitdump), + // compare_by_address); + + if (match == NULL) + return NULL; + + return _sysprof_symbol_new (g_ref_string_acquire (match->name), + NULL, + NULL, + match->address, + match->address + match->code_size, + SYSPROF_SYMBOL_KIND_USER); +} + +static void +sysprof_jitdump_symbolizer_finalize (GObject *object) +{ + SysprofJitdumpSymbolizer *self = (SysprofJitdumpSymbolizer *)object; + + g_clear_pointer (&self->jitdumps, g_array_unref); + + G_OBJECT_CLASS (sysprof_jitdump_symbolizer_parent_class)->finalize (object); +} + +static void +sysprof_jitdump_symbolizer_class_init (SysprofJitdumpSymbolizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSymbolizerClass *symbolizer_class = SYSPROF_SYMBOLIZER_CLASS (klass); + + object_class->finalize = sysprof_jitdump_symbolizer_finalize; + + symbolizer_class->prepare_async = sysprof_jitdump_symbolizer_prepare_async; + symbolizer_class->prepare_finish = sysprof_jitdump_symbolizer_prepare_finish; + symbolizer_class->symbolize = sysprof_jitdump_symbolizer_symbolize; +} + +static void +sysprof_jitdump_symbolizer_init (SysprofJitdumpSymbolizer *self) +{ + self->jitdumps = g_array_new (FALSE, FALSE, sizeof (Jitdump)); + g_array_set_clear_func (self->jitdumps, jitdump_clear); +} + +SysprofSymbolizer * +sysprof_jitdump_symbolizer_new (void) +{ + return g_object_new (SYSPROF_TYPE_JITDUMP_SYMBOLIZER, NULL); +} diff --git a/src/libsysprof/sysprof-jitdump-symbolizer.h b/src/libsysprof/sysprof-jitdump-symbolizer.h new file mode 100644 index 00000000..f737800f --- /dev/null +++ b/src/libsysprof/sysprof-jitdump-symbolizer.h @@ -0,0 +1,51 @@ +/* sysprof-jitdump-symbolizer.h + * + * Copyright 2025 Justin Michaud + * + * 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-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_JITDUMP_SYMBOLIZER (sysprof_jitdump_symbolizer_get_type()) +#define SYSPROF_IS_JITDUMP_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_JITDUMP_SYMBOLIZER) +#define SYSPROF_JITDUMP_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_JITDUMP_SYMBOLIZER, SysprofJitdumpSymbolizer) +#define SYSPROF_JITDUMP_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_JITDUMP_SYMBOLIZER, SysprofJitdumpSymbolizerClass) + +typedef struct _SysprofJitdumpSymbolizer SysprofJitdumpSymbolizer; +typedef struct _SysprofJitdumpSymbolizerClass SysprofJitdumpSymbolizerClass; + +/** + * SysprofJitdumpSymbolizer: + * + * A symbolizer that reads from a Perf JITDump (https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/jitdump-specification.txt). + * + * There is considerable overlap between this format and the JITMap symbolizer used by gjs, however this format + * does not require writing a custom profiler. The target application (for example, JavaScriptCore) mmaps a file + * named JIT_*.dump, and that stores all of the information we need to symbolize the code. + */ +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_jitdump_symbolizer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_jitdump_symbolizer_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofJitdumpSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof.h b/src/libsysprof/sysprof.h index 514e332b..52aa758c 100644 --- a/src/libsysprof/sysprof.h +++ b/src/libsysprof/sysprof.h @@ -64,6 +64,7 @@ G_BEGIN_DECLS # include "sysprof-energy-usage.h" # include "sysprof-enums.h" # include "sysprof-instrument.h" +# include "sysprof-jitdump-symbolizer.h" # include "sysprof-jitmap-symbolizer.h" # include "sysprof-kallsyms-symbolizer.h" # include "sysprof-malloc-tracing.h" diff --git a/src/sysprof/sysprof-recording-template.c b/src/sysprof/sysprof-recording-template.c index be517346..309bb2bd 100644 --- a/src/sysprof/sysprof-recording-template.c +++ b/src/sysprof/sysprof-recording-template.c @@ -810,6 +810,7 @@ sysprof_recording_template_create_loader (SysprofRecordingTemplate *self, sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new ()); sysprof_multi_symbolizer_take (multi, SYSPROF_SYMBOLIZER (g_steal_pointer (&elf))); sysprof_multi_symbolizer_take (multi, sysprof_jitmap_symbolizer_new ()); + sysprof_multi_symbolizer_take (multi, sysprof_jitdump_symbolizer_new ()); sysprof_document_loader_set_symbolizer (loader, SYSPROF_SYMBOLIZER (multi)); -- GitLab From 56a1e58ab965d83d34d6aa8b0ee77e5ba12bab4c Mon Sep 17 00:00:00 2001 From: Justin Michaud Date: Wed, 25 Jun 2025 14:58:42 -0600 Subject: [PATCH 2/5] Add path translation --- src/libsysprof/sysprof-jitdump-symbolizer.c | 91 +++++++++++++-------- src/libsysprof/sysprof-jitdump-symbolizer.h | 2 +- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/src/libsysprof/sysprof-jitdump-symbolizer.c b/src/libsysprof/sysprof-jitdump-symbolizer.c index e92c7a70..bfc260dd 100644 --- a/src/libsysprof/sysprof-jitdump-symbolizer.c +++ b/src/libsysprof/sysprof-jitdump-symbolizer.c @@ -22,12 +22,14 @@ #include -#include "timsort/gtktimsortprivate.h" +#include "sysprof-jitdump-symbolizer.h" +#include "timsort/gtktimsortprivate.h" #include "sysprof-document-private.h" -#include "sysprof-jitdump-symbolizer.h" -#include "sysprof-symbol-private.h" +#include "sysprof-document-process-private.h" +#include "sysprof-strings-private.h" #include "sysprof-symbolizer-private.h" +#include "sysprof-symbol-private.h" typedef struct _Jitdump { @@ -95,6 +97,8 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, const guint32 JIT_CODE_LOAD = 0; Jitdump map; + g_assert (error != NULL); + g_assert (*error == NULL); g_assert (G_IS_DATA_INPUT_STREAM (input)); g_data_input_stream_set_byte_order (input, G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN); @@ -126,6 +130,8 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, return; g_input_stream_skip (G_INPUT_STREAM (input), total_size - sizeof (magic) - sizeof (version) - sizeof (total_size), cancellable, error); + if (*error != NULL) + return; if (magic != 0x4A695444) { @@ -150,7 +156,9 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, { guint32 id; guint32 total_size; - guint64 timestamp; + G_GNUC_UNUSED guint64 timestamp; + const gsize fields_size_header = (2 * sizeof (uint32_t) + sizeof (uint64_t)); + const gsize string_terminator = sizeof (char); if (*error != NULL) return; @@ -163,7 +171,7 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, if (*error != NULL) return; - if (total_size < 2 * sizeof (uint32_t) + sizeof (uint64_t)) + if (total_size < fields_size_header) { *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid record size"); return; @@ -184,20 +192,21 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, // * uint64_t code_index: unique identifier for the jitted code (see below) // * char[n]: function name in ASCII including the null termination // * native code: raw byte encoding of the jitted code - guint32 pid; - guint32 tid; + G_GNUC_UNUSED guint32 pid; + G_GNUC_UNUSED guint32 tid; guint64 vma; guint64 code_addr; guint64 code_size; - guint64 code_index; + G_GNUC_UNUSED guint64 code_index; gsize name_len; g_autofree gchar *name = NULL; g_autofree guint8 *code = NULL; gsize code_read_bytes = 0; gsize name_read_bytes = 0; - - if (total_size < 2 * sizeof (uint32_t) + 4 * sizeof (uint64_t)) + const gsize fields_size_jit_code_load = 2 * sizeof (uint32_t) + 4 * sizeof (uint64_t); + + if (total_size < fields_size_jit_code_load + fields_size_header + string_terminator) { *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid jit load record size"); return; @@ -226,18 +235,23 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, code_index = g_data_input_stream_read_uint64 (input, cancellable, error); if (*error != NULL) return; + + if (code_size >= UINT32_MAX || total_size < fields_size_jit_code_load + fields_size_header + string_terminator + code_size) + { + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code size: %lu", code_size); + return; + } - // Includes null byte - name_len = total_size - code_size - (2 * sizeof (uint32_t) + 4 * sizeof (uint64_t)) - (2 * sizeof (uint32_t) + sizeof (uint64_t)); - name = g_malloc0(name_len); + name_len = total_size - code_size - fields_size_jit_code_load - fields_size_header - string_terminator; + name = g_malloc0(name_len + string_terminator); code = g_malloc0(code_size); - g_input_stream_read_all (G_INPUT_STREAM (input), name, name_len, &name_read_bytes, cancellable, error); + g_input_stream_read_all (G_INPUT_STREAM (input), name, name_len + string_terminator, &name_read_bytes, cancellable, error); if (*error != NULL) return; - if (name_read_bytes != name_len) + if (name_read_bytes != name_len + string_terminator) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record name. Read %lu bytes, expected %lu", name_read_bytes, name_len); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record name. Read %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT, name_read_bytes, name_len + string_terminator); return; } @@ -247,20 +261,16 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, if (code_read_bytes != code_size) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code. Read %lu bytes, expected %lu", code_read_bytes, code_size); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code. Read %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT, code_read_bytes, code_size); return; } if (vma != code_addr) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Unexpected vma: %lu code_addr:%lu", vma, code_addr); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Unexpected vma: %lu code_addr: %lu", vma, code_addr); return; } - (void) pid; - (void) tid; - (void) timestamp; - (void) code_index; map.name = _sysprof_document_ref_string (prepare->document, name); map.address = code_addr; map.code_size = code_size; @@ -313,22 +323,25 @@ sysprof_jitdump_symbolizer_prepare_worker (GTask *task, for (guint i = 0; i < n_processes; i++) { g_autoptr(SysprofDocumentProcess) process = g_list_model_get_item (processes, i); + SysprofProcessInfo* process_info = _sysprof_document_process_get_info (process); + SysprofMountNamespace* mount_namespace = process_info->mount_namespace; g_autoptr(GListModel) memory_maps = sysprof_document_process_list_memory_maps (process); guint n_memory_maps = g_list_model_get_n_items (memory_maps); for (guint j = 0; j < n_memory_maps; j++) { g_autoptr(SysprofDocumentMmap) memory_map = g_list_model_get_item (memory_maps, j); - const char *file_path = sysprof_document_mmap_get_file (memory_map); + const char *file_path_original = sysprof_document_mmap_get_file (memory_map); g_autoptr(GFile) file_handle = NULL; g_autofree char *file_basename = NULL; g_autoptr(GFileInputStream) stream = NULL; g_autoptr(GError) error = NULL; + g_autofree char** translated_file_paths = NULL; - if (file_path == NULL) + if (file_path_original == NULL) continue; - file_handle = g_file_new_for_path (file_path); + file_handle = g_file_new_for_path (file_path_original); file_basename = g_file_get_basename (file_handle); if (file_basename == NULL) @@ -341,9 +354,23 @@ sysprof_jitdump_symbolizer_prepare_worker (GTask *task, continue; stream = g_file_read (file_handle, cancellable, &error); + g_clear_error (&error); + if (mount_namespace != NULL && stream == NULL) + { + translated_file_paths = sysprof_mount_namespace_translate (mount_namespace, file_path_original); + for (gint k = 0; translated_file_paths && translated_file_paths[k]; k++) + { + file_handle = g_file_new_for_path (translated_file_paths[k]); + stream = g_file_read (file_handle, cancellable, &error); + g_clear_error (&error); + if (stream != NULL) + break; + } + } + if (stream == NULL) { - g_warning ("Found reference to Perf JITDump file %s, but file does not exist.", file_path); + g_warning ("Failed to open Perf JITDump file %s", file_path_original); continue; } @@ -354,7 +381,7 @@ sysprof_jitdump_symbolizer_prepare_worker (GTask *task, if (error != NULL) { - g_warning ("Failed to load Perf JITDump file %s: %s", file_path, error->message); + g_warning ("Failed to load Perf JITDump file %s: %s", file_path_original, error->message); g_clear_error (&error); continue; } @@ -367,11 +394,11 @@ sysprof_jitdump_symbolizer_prepare_worker (GTask *task, (GCompareDataFunc)compare_by_address, NULL); - // for (int i = 0; i < self->jitdumps->len; ++i) - // { - // Jitdump *jitdump = &g_array_index (self->jitdumps, Jitdump, i); - // g_warning ("Got dump: %s with address %lx", jitdump->name, jitdump->address); - // } + for (int i = 0; i < self->jitdumps->len && i < 50; ++i) + { + Jitdump *jitdump = &g_array_index (self->jitdumps, Jitdump, i); + g_warning ("Got dump: %s with address %lx", jitdump->name, jitdump->address); + } g_task_return_boolean (task, TRUE); } diff --git a/src/libsysprof/sysprof-jitdump-symbolizer.h b/src/libsysprof/sysprof-jitdump-symbolizer.h index f737800f..584cd7c2 100644 --- a/src/libsysprof/sysprof-jitdump-symbolizer.h +++ b/src/libsysprof/sysprof-jitdump-symbolizer.h @@ -43,7 +43,7 @@ typedef struct _SysprofJitdumpSymbolizerClass SysprofJitdumpSymbolizerClass; */ SYSPROF_AVAILABLE_IN_ALL GType sysprof_jitdump_symbolizer_get_type (void) G_GNUC_CONST; -SYSPROF_AVAILABLE_IN_ALL +SYSPROF_AVAILABLE_IN_49 SysprofSymbolizer *sysprof_jitdump_symbolizer_new (void); G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofJitdumpSymbolizer, g_object_unref) -- GitLab From 2325168a238a5dca882e05a7457ce24ecf5580b3 Mon Sep 17 00:00:00 2001 From: Justin Michaud Date: Wed, 25 Jun 2025 15:32:18 -0600 Subject: [PATCH 3/5] Add symbolizer to bundle --- src/libsysprof/sysprof-symbols-bundle.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libsysprof/sysprof-symbols-bundle.c b/src/libsysprof/sysprof-symbols-bundle.c index 4e04d2ed..b91607ae 100644 --- a/src/libsysprof/sysprof-symbols-bundle.c +++ b/src/libsysprof/sysprof-symbols-bundle.c @@ -107,6 +107,8 @@ sysprof_symbols_bundle_augment_fiber (gpointer user_data) sysprof_multi_symbolizer_take (multi, g_steal_pointer (&debuginfod)); } + sysprof_multi_symbolizer_take (multi, sysprof_jitdump_symbolizer_new ()); + sysprof_document_loader_set_symbolizer (loader, SYSPROF_SYMBOLIZER (multi)); if (!(document = dex_await_object (_sysprof_document_loader_load (loader), &error))) -- GitLab From a791f74bbf0fc2f9098fccf90fd033edeab0f029 Mon Sep 17 00:00:00 2001 From: Justin Michaud Date: Tue, 1 Jul 2025 16:44:11 -0600 Subject: [PATCH 4/5] fix 32-bit build --- src/libsysprof/sysprof-jitdump-symbolizer.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libsysprof/sysprof-jitdump-symbolizer.c b/src/libsysprof/sysprof-jitdump-symbolizer.c index bfc260dd..9edaef2e 100644 --- a/src/libsysprof/sysprof-jitdump-symbolizer.c +++ b/src/libsysprof/sysprof-jitdump-symbolizer.c @@ -238,7 +238,7 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, if (code_size >= UINT32_MAX || total_size < fields_size_jit_code_load + fields_size_header + string_terminator + code_size) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code size: %lu", code_size); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code size: %llu", code_size); return; } @@ -261,13 +261,13 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, if (code_read_bytes != code_size) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code. Read %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT, code_read_bytes, code_size); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code. Read %" G_GSIZE_FORMAT " bytes, expected %llu", code_read_bytes, code_size); return; } if (vma != code_addr) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Unexpected vma: %lu code_addr: %lu", vma, code_addr); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Unexpected vma: %llu code_addr: %llu", vma, code_addr); return; } @@ -284,7 +284,7 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, g_assert_not_reached (); if (skipped != skip) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Failed to skip record: %ld %ld", skip, skipped); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Failed to skip record: %zd %zd", skip, skipped); return; } @@ -320,6 +320,8 @@ sysprof_jitdump_symbolizer_prepare_worker (GTask *task, processes = sysprof_document_list_processes (prepare->document); n_processes = g_list_model_get_n_items (processes); + g_warning ("JITDUMP PREPARE WORKER"); + for (guint i = 0; i < n_processes; i++) { g_autoptr(SysprofDocumentProcess) process = g_list_model_get_item (processes, i); @@ -397,7 +399,7 @@ sysprof_jitdump_symbolizer_prepare_worker (GTask *task, for (int i = 0; i < self->jitdumps->len && i < 50; ++i) { Jitdump *jitdump = &g_array_index (self->jitdumps, Jitdump, i); - g_warning ("Got dump: %s with address %lx", jitdump->name, jitdump->address); + g_warning ("Got dump: %s with address %llx", jitdump->name, jitdump->address); } g_task_return_boolean (task, TRUE); -- GitLab From b1274570c0ffbd30e844caf2a1f1a4ecab07eeb9 Mon Sep 17 00:00:00 2001 From: Justin Michaud Date: Tue, 1 Jul 2025 16:53:11 -0600 Subject: [PATCH 5/5] Update printf formatting --- src/libsysprof/sysprof-jitdump-symbolizer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libsysprof/sysprof-jitdump-symbolizer.c b/src/libsysprof/sysprof-jitdump-symbolizer.c index 9edaef2e..d7480484 100644 --- a/src/libsysprof/sysprof-jitdump-symbolizer.c +++ b/src/libsysprof/sysprof-jitdump-symbolizer.c @@ -238,7 +238,7 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, if (code_size >= UINT32_MAX || total_size < fields_size_jit_code_load + fields_size_header + string_terminator + code_size) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code size: %llu", code_size); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code size: %"G_GUINT64_FORMAT"", code_size); return; } @@ -261,13 +261,13 @@ sysprof_jitdump_symbolizer_load_jitdump (SysprofJitdumpSymbolizer *self, if (code_read_bytes != code_size) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code. Read %" G_GSIZE_FORMAT " bytes, expected %llu", code_read_bytes, code_size); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Invalid jit record code. Read %" G_GSIZE_FORMAT " bytes, expected %"G_GUINT64_FORMAT"", code_read_bytes, code_size); return; } if (vma != code_addr) { - *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Unexpected vma: %llu code_addr: %llu", vma, code_addr); + *error = g_error_new (G_IO_ERROR,G_IO_ERROR_INVALID_DATA, "Unexpected vma: %"G_GUINT64_FORMAT" code_addr: %"G_GUINT64_FORMAT"", vma, code_addr); return; } @@ -399,7 +399,7 @@ sysprof_jitdump_symbolizer_prepare_worker (GTask *task, for (int i = 0; i < self->jitdumps->len && i < 50; ++i) { Jitdump *jitdump = &g_array_index (self->jitdumps, Jitdump, i); - g_warning ("Got dump: %s with address %llx", jitdump->name, jitdump->address); + g_warning ("Got dump: %s with address %"G_GUINT64_FORMAT, jitdump->name, jitdump->address); } g_task_return_boolean (task, TRUE); -- GitLab