diff --git a/src/libsysprof-ui/meson.build b/src/libsysprof-ui/meson.build index 4a84a6c8..406b47ef 100644 --- a/src/libsysprof-ui/meson.build +++ b/src/libsysprof-ui/meson.build @@ -109,7 +109,7 @@ libsysprof_ui = shared_library( 'sysprof-ui-@0@'.format(libsysprof_api_version), libsysprof_ui_public_sources + libsysprof_ui_private_sources + libsysprof_ui_resources, - dependencies: libsysprof_ui_deps, + dependencies: libsysprof_ui_deps + [librax_dep], install_dir: get_option('libdir'), install: true, c_args: [ '-DSYSPROF_UI_COMPILATION' ], diff --git a/src/libsysprof/binfile.c b/src/libsysprof/binfile.c index 145322ef..2cb74187 100644 --- a/src/libsysprof/binfile.c +++ b/src/libsysprof/binfile.c @@ -89,7 +89,8 @@ already_warned (const char *name) } static ElfParser * -get_build_id_file (ElfParser *elf) +get_build_id_file (ElfParser *elf, + const char * const *debug_dirs) { const char *build_id; GList *tries = NULL, *list; @@ -108,12 +109,14 @@ get_build_id_file (ElfParser *elf) init = g_strndup (build_id, 2); rest = g_strdup_printf ("%s%s", build_id + 2, ".debug"); - tmp = g_build_filename ( - "/usr", "lib", "debug", ".build-id", init, rest, NULL); - tries = g_list_append (tries, tmp); - - tmp = g_build_filename (DEBUGDIR, ".build-id", init, rest, NULL); - tries = g_list_append (tries, tmp); + if (debug_dirs) + { + for (guint i = 0; debug_dirs[i]; i++) + { + tmp = g_build_filename (debug_dirs[i], ".build-id", init, rest, NULL); + tries = g_list_append (tries, tmp); + } + } for (list = tries; list != NULL; list = list->next) { @@ -150,11 +153,10 @@ get_debuglink_file (ElfParser *elf, const gchar * const *debug_dirs) { const char *basename; - char *dir; guint32 crc32; ElfParser *result = NULL; const char *build_id; - guint i; + char *dir; if (!elf) return NULL; @@ -172,9 +174,9 @@ get_debuglink_file (ElfParser *elf, dir = g_path_get_dirname (filename); - for (i = 0; debug_dirs[i]; i++) + for (guint i = 0; debug_dirs[i]; i++) { - const char *name = debug_dirs[i]; + char *name = g_build_filename (debug_dirs[i], basename, NULL); ElfParser *parser = elf_parser_new (name, NULL); guint32 file_crc; const char *file_build_id; @@ -194,7 +196,7 @@ get_debuglink_file (ElfParser *elf, if (file_crc == crc32) { result = parser; - *new_name = g_strdup (name); + *new_name = g_steal_pointer (&name); break; } else @@ -209,6 +211,8 @@ get_debuglink_file (ElfParser *elf, skip: elf_parser_free (parser); } + + g_free (name); } g_free (dir); @@ -226,7 +230,7 @@ get_debug_binaries (GList *files, GHashTable *seen_names; GList *free_us = NULL; - build_id_file = get_build_id_file (elf); + build_id_file = get_build_id_file (elf, debug_dirs); if (build_id_file) return g_list_prepend (files, build_id_file); @@ -354,9 +358,6 @@ bin_file_new (const char *filename, ElfParser *elf = NULL; bin_file_t *bf; - if (g_str_has_prefix (filename, "/var/run/host/")) - real_filename = filename + strlen ("/var/run/host"); - bf = g_new0 (bin_file_t, 1); bf->inode_check = FALSE; @@ -475,9 +476,9 @@ bin_file_check_inode (bin_file_t *bin_file, } static const ElfSym * -get_elf_sym (bin_file_t *file, - const bin_symbol_t *symbol, - ElfParser **elf_ret) +get_elf_sym (bin_file_t *file, + const bin_symbol_t *symbol, + ElfParser **elf_ret) { GList *list; @@ -500,7 +501,7 @@ get_elf_sym (bin_file_t *file, } const char * -bin_symbol_get_name (bin_file_t *file, +bin_symbol_get_name (bin_file_t *file, const bin_symbol_t *symbol) { if (file->undefined_name == (char *)symbol) diff --git a/src/libsysprof/elfparser.c b/src/libsysprof/elfparser.c index 376a1f0e..ec427ae0 100644 --- a/src/libsysprof/elfparser.c +++ b/src/libsysprof/elfparser.c @@ -124,6 +124,28 @@ MAKE_SYMBOL_ACCESSOR(st_value); MAKE_SYMBOL_ACCESSOR(st_size); MAKE_SYMBOL_ACCESSOR(st_shndx); +static gboolean +in_container (void) +{ + static gboolean _in_container; + static gboolean initialized; + + if (!initialized) + { + /* Flatpak has /.flatpak-info + * Podman has /run/.containerenv + * + * Both have access to host files via /var/run/host. + */ + _in_container = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS) || + g_file_test ("/run/.containerenv", G_FILE_TEST_EXISTS); + + initialized = TRUE; + } + + return _in_container; +} + static void section_free (Section *section) { @@ -252,17 +274,31 @@ elf_parser_new_from_data (const guchar *data, return parser; } -ElfParser * -elf_parser_new (const char *filename, - GError **err) +static GMappedFile * +open_mapped_file (const char *filename, + GError **error) { + GMappedFile *file; + char *alternate = NULL; + + if (in_container () && !g_str_has_prefix (filename, g_get_home_dir ())) + filename = alternate = g_build_filename ("/var/run/host", filename, NULL); + file = g_mapped_file_new (filename, FALSE, error); + g_free (alternate); + + return file; +} + +ElfParser * +elf_parser_new (const char *filename, + GError **error) +{ + GMappedFile *file; const guchar *data; gsize length; ElfParser *parser; - GMappedFile *file = g_mapped_file_new (filename, FALSE, NULL); - - if (!file) + if (!(file = open_mapped_file (filename, error))) return NULL; #if 0 @@ -284,6 +320,11 @@ elf_parser_new (const char *filename, if (!parser) { + g_set_error (error, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, + "Failed to load ELF from file %s", + filename); g_mapped_file_unref (file); return NULL; } @@ -730,7 +771,8 @@ elf_parser_get_build_id (ElfParser *parser) } const char * -elf_parser_get_debug_link (ElfParser *parser, guint32 *crc32) +elf_parser_get_debug_link (ElfParser *parser, + guint32 *crc32) { guint64 offset; const Section *debug_link = find_section (parser, ".gnu_debuglink", @@ -820,7 +862,3 @@ elf_parser_get_sym_address_range (ElfParser *parser, *begin = sym->address - parser->text_section->load_address; *end = *begin + st_size (parser, sym->table, sym->offset); } - -/* - * Utility functions - */ diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index b95c66e4..3d9c0b2f 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -79,6 +79,7 @@ libsysprof_private_sources = [ 'sysprof-line-reader.c', 'sysprof-map-lookaside.c', 'sysprof-mountinfo.c', + 'sysprof-path-resolver.c', 'sysprof-podman.c', 'sysprof-polkit.c', 'sysprof-symbol-map.c', @@ -90,9 +91,10 @@ libsysprof_private_sources = [ libsysprof_public_sources += libsysprof_capture_sources librax = static_library('rax', ['rax.c'], - c_args: [ '-Wno-declaration-after-statement', - '-Wno-format-nonliteral', - '-Wno-shadow' ], + c_args: [ '-Wno-declaration-after-statement', + '-Wno-format-nonliteral', + '-Wno-shadow' ], + gnu_symbol_visibility: 'hidden', ) librax_dep = declare_dependency( @@ -139,10 +141,7 @@ if host_machine.system() == 'darwin' libsysprof_c_args += [ '-DNT_GNU_BUILD_ID=3', '-DELF_NOTE_GNU="GNU"', '-D__LIBELF_INTERNAL__' ] endif -# Meson's pkgconfig module doesn't understand this one -libsysprof_deps = libsysprof_pkg_deps + [ - librax_dep, -] +libsysprof_deps = libsysprof_pkg_deps libsysprof_libs_private = [] @@ -151,8 +150,8 @@ if host_machine.system() != 'darwin' libsysprof_libs_private += '-lstdc++' endif -libsysprof = shared_library( - 'sysprof-@0@'.format(libsysprof_api_version), +libsysprof_static = static_library( + 'sysprof', libsysprof_public_sources + libsysprof_private_sources, include_directories: [include_directories('.'), @@ -160,9 +159,19 @@ libsysprof = shared_library( libsysprof_capture_include_dirs], dependencies: libsysprof_deps, c_args: libsysprof_c_args, + gnu_symbol_visibility: 'hidden', +) + +libsysprof_static_dep = declare_dependency( + link_whole: libsysprof_static, + dependencies: libsysprof_deps + [librax_dep], + include_directories: [include_directories('.'), libsysprof_capture_include_dirs], +) + +libsysprof = shared_library('sysprof-@0@'.format(libsysprof_api_version), + dependencies: libsysprof_deps + [libsysprof_static_dep], install: true, install_dir: get_option('libdir'), - gnu_symbol_visibility: 'hidden', ) libsysprof_dep = declare_dependency( diff --git a/src/libsysprof/sysprof-elf-symbol-resolver-private.h b/src/libsysprof/sysprof-elf-symbol-resolver-private.h new file mode 100644 index 00000000..77b65ff9 --- /dev/null +++ b/src/libsysprof/sysprof-elf-symbol-resolver-private.h @@ -0,0 +1,33 @@ +/* sysprof-elf-symbol-resolver-private.h + * + * Copyright 2021 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-elf-symbol-resolver.h" + +G_BEGIN_DECLS + +char *_sysprof_elf_symbol_resolver_resolve_path (SysprofElfSymbolResolver *self, + GPid pid, + const char *path); +const char *_sysprof_elf_symbol_resolver_get_pid_kind (SysprofElfSymbolResolver *self, + GPid pid); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-elf-symbol-resolver.c b/src/libsysprof/sysprof-elf-symbol-resolver.c index ddf59173..1e21e74a 100644 --- a/src/libsysprof/sysprof-elf-symbol-resolver.c +++ b/src/libsysprof/sysprof-elf-symbol-resolver.c @@ -20,6 +20,7 @@ #include "config.h" +#include #include #include "binfile.h" @@ -27,16 +28,44 @@ #include "sysprof-elf-symbol-resolver.h" #include "sysprof-flatpak.h" #include "sysprof-map-lookaside.h" +#include "sysprof-path-resolver.h" #include "sysprof-podman.h" +#include "sysprof-symbol-resolver-private.h" + +typedef enum +{ + PROCESS_KIND_STANDARD, + PROCESS_KIND_FLATPAK, + PROCESS_KIND_PODMAN, +} ProcessKind; + +typedef struct +{ + char *on_host; + char *in_process; + int layer; +} ProcessOverlay; + +typedef struct +{ + SysprofMapLookaside *lookaside; + SysprofPathResolver *resolver; + GByteArray *mountinfo_data; + GArray *overlays; + char **debug_dirs; + char *info; + int pid; + guint kind : 2; +} ProcessInfo; struct _SysprofElfSymbolResolver { - GObject parent_instance; + GObject parent_instance; - GArray *debug_dirs; - GHashTable *lookasides; - GHashTable *bin_files; - GHashTable *tag_cache; + GHashTable *processes; + GStringChunk *chunks; + GHashTable *bin_files; + GHashTable *tag_cache; }; static void symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface); @@ -48,19 +77,34 @@ G_DEFINE_TYPE_EXTENDED (SysprofElfSymbolResolver, G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SYMBOL_RESOLVER, symbol_resolver_iface_init)) -static gboolean -is_flatpak (void) +static void +process_info_free (gpointer data) { - static gsize did_init = FALSE; - static gboolean ret; + ProcessInfo *pi = data; - if (g_once_init_enter (&did_init)) + if (pi != NULL) { - ret = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS); - g_once_init_leave (&did_init, TRUE); + g_clear_pointer (&pi->lookaside, sysprof_map_lookaside_free); + g_clear_pointer (&pi->resolver, _sysprof_path_resolver_free); + g_clear_pointer (&pi->mountinfo_data, g_byte_array_unref); + g_clear_pointer (&pi->overlays, g_array_unref); + g_clear_pointer (&pi->debug_dirs, g_strfreev); + g_clear_pointer (&pi->info, g_free); + g_slice_free (ProcessInfo, pi); } +} - return ret; +static const char * const * +process_info_get_debug_dirs (const ProcessInfo *pi) +{ + static const char *standard[] = { "/usr/lib/debug", NULL }; + + if (pi->kind == PROCESS_KIND_FLATPAK) + return standard; /* TODO */ + else if (pi->kind == PROCESS_KIND_PODMAN) + return standard; /* TODO */ + else + return standard; } static void @@ -69,9 +113,9 @@ sysprof_elf_symbol_resolver_finalize (GObject *object) SysprofElfSymbolResolver *self = (SysprofElfSymbolResolver *)object; g_clear_pointer (&self->bin_files, g_hash_table_unref); - g_clear_pointer (&self->lookasides, g_hash_table_unref); g_clear_pointer (&self->tag_cache, g_hash_table_unref); - g_clear_pointer (&self->debug_dirs, g_array_unref); + g_clear_pointer (&self->processes, g_hash_table_unref); + g_clear_pointer (&self->chunks, g_string_chunk_free); G_OBJECT_CLASS (sysprof_elf_symbol_resolver_parent_class)->finalize (object); } @@ -84,149 +128,257 @@ sysprof_elf_symbol_resolver_class_init (SysprofElfSymbolResolverClass *klass) object_class->finalize = sysprof_elf_symbol_resolver_finalize; } -static void -free_element_string (gpointer data) -{ - g_free (*(gchar **)data); -} - static void sysprof_elf_symbol_resolver_init (SysprofElfSymbolResolver *self) { - g_auto(GStrv) podman_dirs = NULL; - - self->debug_dirs = g_array_new (TRUE, FALSE, sizeof (gchar *)); - g_array_set_clear_func (self->debug_dirs, free_element_string); - - sysprof_elf_symbol_resolver_add_debug_dir (self, "/usr/lib/debug"); - sysprof_elf_symbol_resolver_add_debug_dir (self, "/usr/lib32/debug"); - sysprof_elf_symbol_resolver_add_debug_dir (self, "/usr/lib64/debug"); - - /* The user may have podman/toolbox containers */ - podman_dirs = sysprof_podman_debug_dirs (); - for (guint i = 0; podman_dirs[i]; i++) - sysprof_elf_symbol_resolver_add_debug_dir (self, podman_dirs[i]); - - if (is_flatpak ()) - { - g_auto(GStrv) debug_dirs = sysprof_flatpak_debug_dirs (); - - for (guint i = 0; debug_dirs[i]; i++) - sysprof_elf_symbol_resolver_add_debug_dir (self, debug_dirs[i]); - } - - self->lookasides = g_hash_table_new_full (NULL, - NULL, - NULL, - (GDestroyNotify)sysprof_map_lookaside_free); - + self->chunks = g_string_chunk_new (4096); + self->processes = g_hash_table_new_full (NULL, NULL, NULL, process_info_free); self->bin_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)bin_file_free); - self->tag_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); } +static ProcessInfo * +sysprof_elf_symbol_resolver_get_process (SysprofElfSymbolResolver *self, + int pid) +{ + ProcessInfo *pi; + + g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); + + if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid)))) + { + pi = g_slice_new0 (ProcessInfo); + pi->pid = pid; + g_hash_table_insert (self->processes, GINT_TO_POINTER (pid), pi); + } + + return pi; +} + static void sysprof_elf_symbol_resolver_load (SysprofSymbolResolver *resolver, SysprofCaptureReader *reader) { SysprofElfSymbolResolver *self = (SysprofElfSymbolResolver *)resolver; + static const guint8 zero[1] = {0}; SysprofCaptureFrameType type; + g_autoptr(GByteArray) mounts = NULL; + g_autofree char *mounts_data = NULL; + GHashTableIter iter; + ProcessInfo *pi; + gpointer k, v; - g_assert (SYSPROF_IS_SYMBOL_RESOLVER (resolver)); + g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); g_assert (reader != NULL); - sysprof_capture_reader_reset (reader); + g_hash_table_remove_all (self->processes); + /* First we need to load all the /proc/{pid}/mountinfo files so that + * we can discover what files within the processes filesystem namespace + * were mapped and where. We can use that information later to build + * path resolvers that let us locate the files from the host. + */ + sysprof_capture_reader_reset (reader); + while (sysprof_capture_reader_peek_type (reader, &type)) + { + if (type == SYSPROF_CAPTURE_FRAME_FILE_CHUNK) + { + const SysprofCaptureFileChunk *ev; + int out_pid; + + if (!(ev = sysprof_capture_reader_read_file (reader))) + break; + + pi = sysprof_elf_symbol_resolver_get_process (self, ev->frame.pid); + + if (strcmp (ev->path, "/.flatpak-info") == 0) + { + pi->kind = PROCESS_KIND_FLATPAK; + g_free (pi->info); + pi->info = g_strndup ((char *)ev->data, ev->len); + } + else if (strcmp (ev->path, "/run/.containerenv") == 0) + { + pi->kind = PROCESS_KIND_PODMAN; + g_free (pi->info); + pi->info = g_strndup ((char *)ev->data, ev->len); + } + else if (g_str_has_prefix (ev->path, "/proc/") && + g_str_has_suffix (ev->path, "/mountinfo") && + sscanf (ev->path, "/proc/%u/mountinfo", &out_pid) == 1) + { + if (pi->mountinfo_data == NULL) + pi->mountinfo_data = g_byte_array_new (); + if (ev->len) + g_byte_array_append (pi->mountinfo_data, ev->data, ev->len); + } + else if (g_str_equal (ev->path, "/proc/mounts")) + { + if (mounts == NULL) + mounts = g_byte_array_new (); + if (ev->len) + g_byte_array_append (mounts, ev->data, ev->len); + } + } + else if (type == SYSPROF_CAPTURE_FRAME_OVERLAY) + { + const SysprofCaptureOverlay *ev; + ProcessOverlay ov; + + if (!(ev = sysprof_capture_reader_read_overlay (reader))) + break; + + ov.on_host = g_string_chunk_insert_const (self->chunks, ev->data); + ov.in_process = g_string_chunk_insert_const (self->chunks, &ev->data[ev->src_len+1]); + ov.layer = ev->layer; + + pi = sysprof_elf_symbol_resolver_get_process (self, ev->frame.pid); + if (pi->overlays == NULL) + pi->overlays = g_array_new (FALSE, FALSE, sizeof (ProcessOverlay)); + g_array_append_val (pi->overlays, ov); + } + else + { + if (!sysprof_capture_reader_skip (reader)) + break; + } + } + + /* Now make sure we have access to /proc/mounts data. If we do not find it + * within the capture, assume we're running on the same host. + */ + if (mounts != NULL) + { + g_byte_array_append (mounts, zero, 1); + mounts_data = (char *)g_byte_array_free (g_steal_pointer (&mounts), FALSE); + } + + if (mounts_data == NULL) + g_file_get_contents ("/proc/mounts", &mounts_data, NULL, NULL); + + /* Now that we loaded all the mountinfo data, we can create path resolvers + * for each of the processes. Once we have that data we can walk the file + * again to load the map events. + */ + g_hash_table_iter_init (&iter, self->processes); + while (g_hash_table_iter_next (&iter, &k, &v)) + { + pi = v; + + if (pi->mountinfo_data == NULL) + continue; + + g_byte_array_append (pi->mountinfo_data, zero, 1); + + pi->resolver = _sysprof_path_resolver_new (mounts_data, + (const char *)pi->mountinfo_data->data); + + if (pi->overlays != NULL) + { + for (guint i = 0; i < pi->overlays->len; i++) + { + const ProcessOverlay *ov = &g_array_index (pi->overlays, ProcessOverlay, i); + _sysprof_path_resolver_add_overlay (pi->resolver, ov->in_process, ov->on_host, ov->layer); + } + } + + if (pi->kind == PROCESS_KIND_FLATPAK) + { + if (pi->info != NULL) + { + g_autoptr(GKeyFile) keyfile = g_key_file_new (); + + if (g_key_file_load_from_data (keyfile, pi->info, (gsize)-1, 0, NULL)) + { + if (g_key_file_has_group (keyfile, "Instance")) + { + g_autofree gchar *app_path = g_key_file_get_string (keyfile, "Instance", "app-path", NULL); + g_autofree gchar *runtime_path = g_key_file_get_string (keyfile, "Instance", "runtime-path", NULL); + + pi->debug_dirs = g_new0 (gchar *, 3); + pi->debug_dirs[0] = g_build_filename (app_path, "lib", "debug", NULL); + pi->debug_dirs[1] = g_build_filename (runtime_path, "lib", "debug", NULL); + pi->debug_dirs[2] = 0; + + /* TODO: Need to locate .Debug version of runtimes. Also, extensions? */ + } + } + } + } + else if (pi->kind == PROCESS_KIND_PODMAN) + { + pi->debug_dirs = g_new0 (gchar *, 2); + pi->debug_dirs[0] = _sysprof_path_resolver_resolve (pi->resolver, "/usr/lib/debug"); + pi->debug_dirs[1] = 0; + } + } + + /* Walk through the file again and extract maps so long as + * we have a resolver for them already. + */ + sysprof_capture_reader_reset (reader); while (sysprof_capture_reader_peek_type (reader, &type)) { if (type == SYSPROF_CAPTURE_FRAME_MAP) { const SysprofCaptureMap *ev = sysprof_capture_reader_read_map (reader); - SysprofMapLookaside *lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (ev->frame.pid)); const char *filename = ev->filename; + g_autofree char *resolved = NULL; SysprofMap map; + pi = sysprof_elf_symbol_resolver_get_process (self, ev->frame.pid); + + if (pi->resolver != NULL) + { + resolved = _sysprof_path_resolver_resolve (pi->resolver, filename); + + if (resolved) + filename = resolved; + } + map.start = ev->start; map.end = ev->end; map.offset = ev->offset; map.inode = ev->inode; map.filename = filename; - if (lookaside == NULL) - { - lookaside = sysprof_map_lookaside_new (); - g_hash_table_insert (self->lookasides, GINT_TO_POINTER (ev->frame.pid), lookaside); - } + if (pi->lookaside == NULL) + pi->lookaside = sysprof_map_lookaside_new (); - sysprof_map_lookaside_insert (lookaside, &map); - } - else if (type == SYSPROF_CAPTURE_FRAME_OVERLAY) - { - const SysprofCaptureOverlay *ev = sysprof_capture_reader_read_overlay (reader); - SysprofMapLookaside *lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (ev->frame.pid)); - const char *src = ev->data; - const char *dst = &ev->data[ev->src_len+1]; - - if (lookaside == NULL) - { - lookaside = sysprof_map_lookaside_new (); - g_hash_table_insert (self->lookasides, GINT_TO_POINTER (ev->frame.pid), lookaside); - } - - sysprof_map_lookaside_overlay (lookaside, src, dst); + sysprof_map_lookaside_insert (pi->lookaside, &map); } else { if (!sysprof_capture_reader_skip (reader)) return; - continue; } } } static bin_file_t * sysprof_elf_symbol_resolver_get_bin_file (SysprofElfSymbolResolver *self, - const SysprofMapOverlay *overlays, - guint n_overlays, + const ProcessInfo *pi, const gchar *filename) { + g_autofree char *alternate = NULL; + const char * const *debug_dirs; + g_autofree char *on_host = NULL; bin_file_t *bin_file; g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); - bin_file = g_hash_table_lookup (self->bin_files, filename); + if ((bin_file = g_hash_table_lookup (self->bin_files, filename))) + return bin_file; - if (bin_file == NULL) - { - g_autofree gchar *path = NULL; - const gchar *alternate = filename; - const gchar * const *dirs; - - dirs = (const gchar * const *)(gpointer)self->debug_dirs->data; - - if (overlays && filename[0] != '/' && filename[0] != '[') - { - for (guint i = 0; i < n_overlays; i++) - { - if (g_str_has_prefix (filename, overlays[i].dst+1)) - { - alternate = path = g_build_filename (overlays[i].src, filename, NULL); - break; - } - } - } - else if (is_flatpak () && g_str_has_prefix (filename, "/usr/")) - { - alternate = path = g_build_filename ("/var/run/host", alternate, NULL); - } - - bin_file = bin_file_new (alternate, dirs); - - g_hash_table_insert (self->bin_files, g_strdup (filename), bin_file); - } + /* Debug dirs are going to be dependent on the process as different + * containers may affect where the debug symbols are installed. + */ + debug_dirs = process_info_get_debug_dirs (pi); + bin_file = bin_file_new (filename, (const char * const *)debug_dirs); + g_hash_table_insert (self->bin_files, g_strdup (filename), bin_file); return bin_file; } @@ -349,15 +501,13 @@ sysprof_elf_symbol_resolver_resolve_full (SysprofElfSymbolResolver *self, gchar **name, GQuark *tag) { - SysprofMapLookaside *lookaside; - const SysprofMapOverlay *overlays = NULL; const bin_symbol_t *bin_sym; const gchar *bin_sym_name; const SysprofMap *map; + ProcessInfo *pi; bin_file_t *bin_file; gulong ubegin; gulong uend; - guint n_overlays = 0; g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); g_assert (name != NULL); @@ -369,27 +519,23 @@ sysprof_elf_symbol_resolver_resolve_full (SysprofElfSymbolResolver *self, if (context != SYSPROF_ADDRESS_CONTEXT_USER) return FALSE; - lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (pid)); - if G_UNLIKELY (lookaside == NULL) + if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid)))) return FALSE; - map = sysprof_map_lookaside_lookup (lookaside, address); + map = sysprof_map_lookaside_lookup (pi->lookaside, address); if G_UNLIKELY (map == NULL) return FALSE; address -= map->start; address += map->offset; - if (lookaside->overlays) - { - overlays = &g_array_index (lookaside->overlays, SysprofMapOverlay, 0); - n_overlays = lookaside->overlays->len; - } - - bin_file = sysprof_elf_symbol_resolver_get_bin_file (self, overlays, n_overlays, map->filename); + bin_file = sysprof_elf_symbol_resolver_get_bin_file (self, pi, map->filename); g_assert (bin_file != NULL); + /* PERF_RECORD_MMAP doesn't provide an inode, so we can't rely on that + * until we can get PERF_RECORD_MMAP2. + */ if G_UNLIKELY (map->inode && !bin_file_check_inode (bin_file, map->inode)) { *name = g_strdup_printf ("%s: inode mismatch", map->filename); @@ -460,22 +606,47 @@ void sysprof_elf_symbol_resolver_add_debug_dir (SysprofElfSymbolResolver *self, const gchar *debug_dir) { - gchar *val; - - g_return_if_fail (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); - g_return_if_fail (debug_dir != NULL); - - if (!g_file_test (debug_dir, G_FILE_TEST_EXISTS)) - return; - - for (guint i = 0; i < self->debug_dirs->len; i++) - { - gchar * const *str = &g_array_index (self->debug_dirs, gchar *, i); - - if (g_strcmp0 (*str, debug_dir) == 0) - return; - } - - val = g_strdup (debug_dir); - g_array_append_val (self->debug_dirs, val); + /* Do Nothing */ + /* XXX: Mark as deprecated post 41 or remove with Gtk4 port */ +} + +char * +_sysprof_elf_symbol_resolver_resolve_path (SysprofElfSymbolResolver *self, + GPid pid, + const char *path) +{ + ProcessInfo *pi; + + g_return_val_if_fail (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self), NULL); + + if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid)))) + return NULL; + + if (pi->resolver == NULL) + return NULL; + + return _sysprof_path_resolver_resolve (pi->resolver, path); +} + +const char * +_sysprof_elf_symbol_resolver_get_pid_kind (SysprofElfSymbolResolver *self, + GPid pid) +{ + ProcessInfo *pi; + + g_return_val_if_fail (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self), NULL); + + if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid)))) + return "unknown"; + + if (pi->kind == PROCESS_KIND_FLATPAK) + return "Flatpak"; + + if (pi->kind == PROCESS_KIND_PODMAN) + return "Podman"; + + if (pi->kind == PROCESS_KIND_STANDARD) + return "Standard"; + + return "unknown"; } diff --git a/src/libsysprof/sysprof-mountinfo.c b/src/libsysprof/sysprof-mountinfo.c index 9a4a05db..826ce8d6 100644 --- a/src/libsysprof/sysprof-mountinfo.c +++ b/src/libsysprof/sysprof-mountinfo.c @@ -298,6 +298,9 @@ sysprof_mountinfo_parse_mountinfo_line (SysprofMountinfo *self, while (*src == '/') src++; + if (*src == 0) + return; + if (prefix != NULL) m.host_path = g_build_filename (prefix, src, NULL); else @@ -340,4 +343,10 @@ sysprof_mountinfo_parse_mountinfo (SysprofMountinfo *self, sysprof_mountinfo_parse_mountinfo_line (self, lines[i]); g_array_sort (self->mountinfos, sort_by_length); + + for (guint i = 0; i < self->mountinfos->len; i++) + { + const Mountinfo *m = &g_array_index (self->mountinfos, Mountinfo, i); + g_print ("MM %s => %s\n", m->host_path, m->mount_path); + } } diff --git a/src/libsysprof/sysprof-path-resolver.c b/src/libsysprof/sysprof-path-resolver.c new file mode 100644 index 00000000..7d7d9bee --- /dev/null +++ b/src/libsysprof/sysprof-path-resolver.c @@ -0,0 +1,554 @@ +/* sysprof-path-resolver.c + * + * Copyright 2021 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-path-resolver.h" + +struct _SysprofPathResolver +{ + GArray *mounts; + GArray *mountpoints; +}; + +typedef struct +{ + /* The path on the host system */ + char *on_host; + + /* The path inside the process domain */ + char *in_process; + + /* The length of @in_process in bytes */ + guint in_process_len; + + /* The depth of the mount (for FUSE overlays) */ + int depth; +} Mountpoint; + +typedef struct +{ + char *device; + char *mountpoint; + char *filesystem; + char *subvolid; + char *subvol; +} Mount; + +typedef struct _st_mountinfo +{ + char *id; + char *parent_id; + char *st_dev; + char *root; + char *mount_point; + char *mount_options; + char *filesystem; + char *mount_source; + char *super_options; +} st_mountinfo; + +static void +clear_st_mountinfo (st_mountinfo *st) +{ + g_free (st->id); + g_free (st->parent_id); + g_free (st->st_dev); + g_free (st->root); + g_free (st->mount_point); + g_free (st->mount_options); + g_free (st->filesystem); + g_free (st->mount_source); + g_free (st->super_options); +} + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (st_mountinfo, clear_st_mountinfo) + +static void +clear_mount (Mount *m) +{ + g_clear_pointer (&m->device, g_free); + g_clear_pointer (&m->mountpoint, g_free); + g_clear_pointer (&m->subvolid, g_free); + g_clear_pointer (&m->subvol, g_free); + g_clear_pointer (&m->filesystem, g_free); +} + +static void +clear_mountpoint (Mountpoint *mp) +{ + g_clear_pointer (&mp->on_host, g_free); + g_clear_pointer (&mp->in_process, g_free); +} + +static gboolean +ignore_fs (const char *fs) +{ + static gsize initialized; + static GHashTable *ignored; + + if (g_once_init_enter (&initialized)) + { + ignored = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_add (ignored, (char *)"autofs"); + g_hash_table_add (ignored, (char *)"binfmt_misc"); + g_hash_table_add (ignored, (char *)"bpf"); + g_hash_table_add (ignored, (char *)"cgroup"); + g_hash_table_add (ignored, (char *)"cgroup2"); + g_hash_table_add (ignored, (char *)"configfs"); + g_hash_table_add (ignored, (char *)"debugfs"); + g_hash_table_add (ignored, (char *)"devpts"); + g_hash_table_add (ignored, (char *)"devtmpfs"); + g_hash_table_add (ignored, (char *)"efivarfs"); + g_hash_table_add (ignored, (char *)"fusectl"); + g_hash_table_add (ignored, (char *)"hugetlbfs"); + g_hash_table_add (ignored, (char *)"mqueue"); + g_hash_table_add (ignored, (char *)"none"); + g_hash_table_add (ignored, (char *)"portal"); + g_hash_table_add (ignored, (char *)"proc"); + g_hash_table_add (ignored, (char *)"pstore"); + g_hash_table_add (ignored, (char *)"ramfs"); + g_hash_table_add (ignored, (char *)"rpc_pipefs"); + g_hash_table_add (ignored, (char *)"securityfs"); + g_hash_table_add (ignored, (char *)"selinuxfs"); + g_hash_table_add (ignored, (char *)"sunrpc"); + g_hash_table_add (ignored, (char *)"sysfs"); + g_hash_table_add (ignored, (char *)"systemd-1"); + g_hash_table_add (ignored, (char *)"tmpfs"); + g_hash_table_add (ignored, (char *)"tracefs"); + g_once_init_leave (&initialized, (gsize)1); + } + + if (g_str_has_prefix (fs, "fuse.")) + return TRUE; + + return g_hash_table_contains (ignored, fs); +} + +static char * +path_copy_with_trailing_slash (const char *path) +{ + if (g_str_has_suffix (path, "/")) + return g_strdup (path); + else + return g_strdup_printf ("%s/", path); +} + +static void +decode_space (char **str) +{ + /* Replace encoded space "\040" with ' ' */ + if (strstr (*str, "\\040") != NULL) + { + g_auto(GStrv) parts = g_strsplit (*str, "\\040", 0); + g_free (*str); + *str = g_strjoinv (" ", parts); + } +} + +static char * +strdup_decode_space (const char *str) +{ + char *copy = g_strdup (str); + decode_space (©); + return copy; +} + +static gboolean +has_prefix_or_equal (const char *str, + const char *prefix) +{ + return g_str_has_prefix (str, prefix) || strcmp (str, prefix) == 0; +} + +static char * +get_option (const char *options, + const char *option) +{ + g_auto(GStrv) parts = NULL; + + g_assert (option != NULL); + g_assert (g_str_has_suffix (option, "=")); + + if (options == NULL) + return NULL; + + parts = g_strsplit (options, ",", 0); + + for (guint i = 0; parts[i] != NULL; i++) + { + if (g_str_has_prefix (parts[i], option)) + { + const char *ret = parts[i] + strlen (option); + + /* Easier to handle "" as NULL */ + if (*ret == 0) + return NULL; + + return g_strdup (ret); + } + } + + return NULL; +} + +static gboolean +parse_st_mountinfo (st_mountinfo *mi, + const char *line) +{ + g_auto(GStrv) parts = NULL; + guint i; + + g_assert (mi != NULL); + g_assert (line != NULL); + + memset (mi, 0, sizeof *mi); + + parts = g_strsplit (line, " ", 0); + if (g_strv_length (parts) < 10) + return FALSE; + + mi->id = g_strdup (parts[0]); + mi->parent_id = g_strdup (parts[1]); + mi->st_dev = g_strdup (parts[2]); + mi->root = strdup_decode_space (parts[3]); + mi->mount_point = strdup_decode_space (parts[4]); + mi->mount_options = strdup_decode_space (parts[5]); + + for (i = 6; parts[i] != NULL && !g_str_equal (parts[i], "-"); i++) + { + /* Do nothing. We just want to skip until after the optional tags + * section which is finished with " - ". + */ + } + + /* Skip past - if there is anything */ + if (parts[i] == NULL || parts[++i] == NULL) + return TRUE; + + /* Get filesystem if provided */ + mi->filesystem = g_strdup (parts[i++]); + if (parts[i] == NULL) + return TRUE; + + /* Get mount source if provided */ + mi->mount_source = strdup_decode_space (parts[i++]); + if (parts[i] == NULL) + return TRUE; + + /* Get super options if provided */ + mi->super_options = strdup_decode_space (parts[i++]); + if (parts[i] == NULL) + return TRUE; + + /* Perhaps mountinfo will be extended once again ... */ + + return TRUE; +} + +static void +parse_mounts (SysprofPathResolver *self, + const char *mounts) +{ + g_auto(GStrv) lines = NULL; + + g_assert (self != NULL); + g_assert (self->mounts != NULL); + g_assert (mounts != NULL); + + lines = g_strsplit (mounts, "\n", 0); + + for (guint i = 0; lines[i]; i++) + { + g_auto(GStrv) parts = g_strsplit (lines[i], " ", 5); + g_autofree char *subvolid = NULL; + g_autofree char *subvol = NULL; + const char *filesystem; + const char *mountpoint; + const char *device; + const char *options; + Mount m; + + /* Field 0: device + * Field 1: mountpoint + * Field 2: filesystem + * Field 3: Options + * .. Ignored .. + */ + if (g_strv_length (parts) != 5) + continue; + + filesystem = parts[2]; + if (ignore_fs (filesystem)) + continue; + + for (guint j = 0; parts[j]; j++) + decode_space (&parts[j]); + + device = parts[0]; + mountpoint = parts[1]; + options = parts[3]; + + + if (g_strcmp0 (filesystem, "btrfs") == 0) + { + subvolid = get_option (options, "subvolid="); + subvol = get_option (options, "subvol="); + } + + m.device = g_strdup (device); + m.filesystem = g_strdup (filesystem); + m.mountpoint = path_copy_with_trailing_slash (mountpoint); + m.subvolid = g_steal_pointer (&subvolid); + m.subvol = g_steal_pointer (&subvol); + + g_array_append_val (self->mounts, m); + } +} + +static const Mount * +find_mount (SysprofPathResolver *self, + const st_mountinfo *mi) +{ + g_autofree char *subvolid = NULL; + + g_assert (self != NULL); + g_assert (mi != NULL); + + subvolid = get_option (mi->super_options, "subvolid="); + + for (guint i = 0; i < self->mounts->len; i++) + { + const Mount *mount = &g_array_index (self->mounts, Mount, i); + + if (g_strcmp0 (mount->device, mi->mount_source) == 0) + { + /* Sanity check that filesystems match */ + if (g_strcmp0 (mount->filesystem, mi->filesystem) != 0) + continue; + + /* If we have a subvolume (btrfs) make sure they match */ + if (subvolid == NULL || + g_strcmp0 (subvolid, mount->subvolid) == 0) + return mount; + } + } + + return NULL; +} + +static void +parse_mountinfo_line (SysprofPathResolver *self, + const char *line) +{ + g_auto(st_mountinfo) st_mi = {0}; + g_autofree char *subvol = NULL; + const char *path; + const Mount *mount; + Mountpoint mp = {0}; + + g_assert (self != NULL); + g_assert (self->mounts != NULL); + g_assert (self->mountpoints != NULL); + + if (!parse_st_mountinfo (&st_mi, line)) + return; + + if (ignore_fs (st_mi.filesystem)) + return; + + if (!(mount = find_mount (self, &st_mi))) + return; + + subvol = get_option (st_mi.super_options, "subvol="); + path = st_mi.root; + + /* If the mount root has a prefix of the subvolume, then + * subtract that from the path (as we will resolve relative + * to the location where it is mounted via the Mount.mountpoint. + */ + if (subvol != NULL && has_prefix_or_equal (path, subvol)) + { + path += strlen (subvol); + if (*path == 0) + path = NULL; + } + + if (path == NULL) + { + mp.on_host = g_strdup (mount->mountpoint); + } + else + { + while (*path == '/') + path++; + mp.on_host = g_build_filename (mount->mountpoint, path, NULL); + } + + if (g_str_has_suffix (mp.on_host, "/") && + !g_str_has_suffix (st_mi.mount_point, "/")) + mp.in_process = g_build_filename (st_mi.mount_point, "/", NULL); + else + mp.in_process = g_strdup (st_mi.mount_point); + + mp.in_process_len = strlen (mp.in_process); + mp.depth = -1; + + g_array_append_val (self->mountpoints, mp); +} + +static gint +sort_by_length (gconstpointer a, + gconstpointer b) +{ + const Mountpoint *mpa = a; + const Mountpoint *mpb = b; + gsize alen = strlen (mpa->in_process); + gsize blen = strlen (mpb->in_process); + + if (alen > blen) + return -1; + else if (blen > alen) + return 1; + + if (mpa->depth < mpb->depth) + return -1; + else if (mpa->depth > mpb->depth) + return 1; + + return 0; +} + +static void +parse_mountinfo (SysprofPathResolver *self, + const char *mountinfo) +{ + g_auto(GStrv) lines = NULL; + + g_assert (self != NULL); + g_assert (self->mounts != NULL); + g_assert (self->mountpoints != NULL); + g_assert (mountinfo != NULL); + + lines = g_strsplit (mountinfo, "\n", 0); + + for (guint i = 0; lines[i]; i++) + parse_mountinfo_line (self, lines[i]); + + g_array_sort (self->mountpoints, sort_by_length); +} + +SysprofPathResolver * +_sysprof_path_resolver_new (const char *mounts, + const char *mountinfo) +{ + SysprofPathResolver *self; + + self = g_slice_new0 (SysprofPathResolver); + + self->mounts = g_array_new (FALSE, FALSE, sizeof (Mount)); + self->mountpoints = g_array_new (FALSE, FALSE, sizeof (Mountpoint)); + + g_array_set_clear_func (self->mounts, (GDestroyNotify)clear_mount); + g_array_set_clear_func (self->mountpoints, (GDestroyNotify)clear_mountpoint); + + if (mounts != NULL) + parse_mounts (self, mounts); + + if (mountinfo != NULL) + parse_mountinfo (self, mountinfo); + + return self; +} + +void +_sysprof_path_resolver_free (SysprofPathResolver *self) +{ + g_clear_pointer (&self->mountpoints, g_array_unref); + g_clear_pointer (&self->mounts, g_array_unref); + g_slice_free (SysprofPathResolver, self); +} + +void +_sysprof_path_resolver_add_overlay (SysprofPathResolver *self, + const char *in_process, + const char *on_host, + int depth) +{ + Mountpoint mp; + + g_return_if_fail (self != NULL); + g_return_if_fail (in_process != NULL); + g_return_if_fail (on_host != NULL); + + mp.in_process = path_copy_with_trailing_slash (in_process); + mp.in_process_len = strlen (mp.in_process); + mp.on_host = path_copy_with_trailing_slash (on_host); + mp.depth = depth; + + g_array_append_val (self->mountpoints, mp); + g_array_sort (self->mountpoints, sort_by_length); +} + +char * +_sysprof_path_resolver_resolve (SysprofPathResolver *self, + const char *path) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (path != NULL, NULL); + + /* TODO: Cache the directory name of @path and use that for followup + * searches like we did in SysprofMountinfo. + */ + + for (guint i = 0; i < self->mountpoints->len; i++) + { + const Mountpoint *mp = &g_array_index (self->mountpoints, Mountpoint, i); + + if (g_str_has_prefix (path, mp->in_process)) + { + g_autofree char *dst = g_build_filename (mp->on_host, + path + mp->in_process_len, + NULL); + + /* If the depth is > -1, then we are dealing with an overlay. We + * unfortunately have to stat() to see if the file exists and then + * skip over this if not. + * + * TODO: This is going to break when we are recording from within + * flatpak as we'll not be able to stat files unless they are + * within the current users home, so system containers would + * be unlilkely to resolve. + */ + if (mp->depth > -1) + { + if (g_file_test (dst, G_FILE_TEST_EXISTS)) + return g_steal_pointer (&dst); + continue; + } + + return g_steal_pointer (&dst); + } + } + + return NULL; +} diff --git a/src/libsysprof/sysprof-path-resolver.h b/src/libsysprof/sysprof-path-resolver.h new file mode 100644 index 00000000..5267cf96 --- /dev/null +++ b/src/libsysprof/sysprof-path-resolver.h @@ -0,0 +1,41 @@ +/* sysprof-path-resolver.h + * + * Copyright 2021 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 + +G_BEGIN_DECLS + +typedef struct _SysprofPathResolver SysprofPathResolver; + +SysprofPathResolver *_sysprof_path_resolver_new (const char *mounts, + const char *mountinfo); +void _sysprof_path_resolver_add_overlay (SysprofPathResolver *self, + const char *in_process, + const char *on_host, + int depth); +void _sysprof_path_resolver_free (SysprofPathResolver *self); +char *_sysprof_path_resolver_resolve (SysprofPathResolver *self, + const char *path); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofPathResolver, _sysprof_path_resolver_free) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-podman.c b/src/libsysprof/sysprof-podman.c index cc690f31..880bc6cc 100644 --- a/src/libsysprof/sysprof-podman.c +++ b/src/libsysprof/sysprof-podman.c @@ -22,6 +22,8 @@ #include "config.h" +#include + #include "sysprof-podman.h" static const char *debug_dirs[] = { @@ -40,7 +42,7 @@ _sysprof_podman_debug_dirs (GPtrArray *dirs) g_assert (dirs != NULL); base_path = g_build_filename (g_get_user_data_dir (), - "containres", + "containers", "storage", "overlay", NULL); @@ -67,3 +69,250 @@ sysprof_podman_debug_dirs (void) g_ptr_array_add (dirs, NULL); return (gchar **)g_ptr_array_free (dirs, FALSE); } + +struct _SysprofPodman +{ + JsonParser *containers_parser; + JsonParser *layers_parser; + JsonParser *images_parser; +}; + +void +sysprof_podman_free (SysprofPodman *self) +{ + g_clear_object (&self->containers_parser); + g_clear_object (&self->layers_parser); + g_clear_object (&self->images_parser); + g_slice_free (SysprofPodman, self); +} + +static void +load_containers (SysprofPodman *self) +{ + g_autofree char *path = NULL; + + g_assert (self != NULL); + + path = g_build_filename (g_get_user_data_dir (), + "containers", + "storage", + "overlay-containers", + "containers.json", + NULL); + json_parser_load_from_file (self->containers_parser, path, NULL); +} + +static void +load_layers (SysprofPodman *self) +{ + g_autofree char *path = NULL; + + g_assert (self != NULL); + + path = g_build_filename (g_get_user_data_dir (), + "containers", + "storage", + "overlay-layers", + "layers.json", + NULL); + json_parser_load_from_file (self->layers_parser, path, NULL); +} + +static void +load_images (SysprofPodman *self) +{ + g_autofree char *path = NULL; + + g_assert (self != NULL); + + path = g_build_filename (g_get_user_data_dir (), + "containers", + "storage", + "overlay-images", + "images.json", + NULL); + json_parser_load_from_file (self->images_parser, path, NULL); +} + +SysprofPodman * +sysprof_podman_snapshot_current_user (void) +{ + SysprofPodman *self; + + self = g_slice_new0 (SysprofPodman); + self->containers_parser = json_parser_new (); + self->layers_parser = json_parser_new (); + self->images_parser = json_parser_new (); + + load_containers (self); + load_layers (self); + load_images (self); + + return self; +} + +static const char * +find_image_layer (JsonParser *parser, + const char *image) +{ + JsonNode *root; + JsonArray *ar; + guint n_items; + + g_assert (JSON_IS_PARSER (parser)); + g_assert (image != NULL); + + if (!(root = json_parser_get_root (parser)) || + !JSON_NODE_HOLDS_ARRAY (root) || + !(ar = json_node_get_array (root))) + return NULL; + + n_items = json_array_get_length (ar); + + for (guint i = 0; i < n_items; i++) + { + JsonObject *item = json_array_get_object_element (ar, i); + const char *id; + const char *layer; + + if (item == NULL || + !json_object_has_member (item, "id") || + !json_object_has_member (item, "layer") || + !(id = json_object_get_string_member (item, "id")) || + strcmp (id, image) != 0 || + !(layer = json_object_get_string_member (item, "layer"))) + continue; + + return layer; + } + + return NULL; +} + +static const char * +find_parent_layer (JsonParser *parser, + const char *layer, + GHashTable *seen) +{ + JsonNode *root; + JsonArray *ar; + guint n_items; + + g_assert (JSON_IS_PARSER (parser)); + g_assert (layer != NULL); + g_assert (seen != NULL); + + if (!(root = json_parser_get_root (parser)) || + !JSON_NODE_HOLDS_ARRAY (root) || + !(ar = json_node_get_array (root))) + return NULL; + + n_items = json_array_get_length (ar); + + for (guint i = 0; i < n_items; i++) + { + JsonObject *item = json_array_get_object_element (ar, i); + const char *parent; + const char *id; + + if (item == NULL || + !json_object_has_member (item, "id") || + !json_object_has_member (item, "parent") || + !(id = json_object_get_string_member (item, "id")) || + strcmp (id, layer) != 0 || + !(parent = json_object_get_string_member (item, "parent"))) + continue; + + if (g_hash_table_contains (seen, parent)) + return NULL; + + return parent; + } + + return NULL; +} + +static char * +get_layer_dir (const char *layer) +{ + /* We don't use XDG data dir because this might be in a container + * or flatpak environment that doesn't match. And generally, it's + * always .local. + */ + return g_build_filename (g_get_home_dir (), + ".local", + "share", + "containers", + "storage", + "overlay", + layer, + "diff", + NULL); +} + +gchar ** +sysprof_podman_get_layers (SysprofPodman *self, + const char *container) +{ + const char *layer = NULL; + const char *image = NULL; + GHashTable *layers; + JsonNode *root; + JsonArray *ar; + const char **keys; + char **ret; + guint n_items; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (container != NULL, NULL); + + if (!(root = json_parser_get_root (self->containers_parser)) || + !JSON_NODE_HOLDS_ARRAY (root) || + !(ar = json_node_get_array (root))) + return NULL; + + n_items = json_array_get_length (ar); + + /* First try to locate the "layer" identifier for the container + * in question. + */ + for (guint i = 0; i < n_items; i++) + { + JsonObject *item = json_array_get_object_element (ar, i); + const char *item_layer; + const char *id; + + if (item == NULL || + !(id = json_object_get_string_member (item, "id")) || + strcmp (id, container) != 0 || + !(item_layer = json_object_get_string_member (item, "layer"))) + continue; + + layer = item_layer; + image = json_object_get_string_member (item, "image"); + break; + } + + layers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + /* Add all our known layers starting from current layer */ + do + g_hash_table_add (layers, get_layer_dir (layer)); + while ((layer = find_parent_layer (self->layers_parser, layer, layers))); + + /* If an image was specified, add its layer */ + if ((layer = find_image_layer (self->images_parser, image))) + { + do + g_hash_table_add (layers, get_layer_dir (layer)); + while ((layer = find_parent_layer (self->layers_parser, layer, layers))); + } + + keys = (const char **)g_hash_table_get_keys_as_array (layers, NULL); + ret = g_strdupv ((char **)keys); + + g_hash_table_unref (layers); + g_free (keys); + + return ret; +} diff --git a/src/libsysprof/sysprof-podman.h b/src/libsysprof/sysprof-podman.h index b113f6f4..053c6259 100644 --- a/src/libsysprof/sysprof-podman.h +++ b/src/libsysprof/sysprof-podman.h @@ -24,6 +24,14 @@ G_BEGIN_DECLS -gchar **sysprof_podman_debug_dirs (void); +typedef struct _SysprofPodman SysprofPodman; + +gchar **sysprof_podman_debug_dirs (void); +SysprofPodman *sysprof_podman_snapshot_current_user (void); +gchar **sysprof_podman_get_layers (SysprofPodman *self, + const char *container); +void sysprof_podman_free (SysprofPodman *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofPodman, sysprof_podman_free) G_END_DECLS diff --git a/src/libsysprof/sysprof-proc-source.c b/src/libsysprof/sysprof-proc-source.c index 937a6ea8..2a9b8c61 100644 --- a/src/libsysprof/sysprof-proc-source.c +++ b/src/libsysprof/sysprof-proc-source.c @@ -45,7 +45,7 @@ #include #include "sysprof-helpers.h" -#include "sysprof-mountinfo.h" +#include "sysprof-podman.h" #include "sysprof-proc-source.h" struct _SysprofProcSource @@ -53,17 +53,10 @@ struct _SysprofProcSource GObject parent_instance; SysprofCaptureWriter *writer; GArray *pids; - SysprofMountinfo *mountinfo; - GHashTable *podman; + SysprofPodman *podman; guint is_ready : 1; }; -typedef struct -{ - char *id; - char **layers; -} PodmanInfo; - static void source_iface_init (SysprofSourceInterface *iface); G_DEFINE_TYPE_EXTENDED (SysprofProcSource, sysprof_proc_source, G_TYPE_OBJECT, 0, @@ -88,33 +81,26 @@ static void sysprof_proc_source_populate_maps (SysprofProcSource *self, GPid pid, const gchar *mapsstr, - const gchar *mountinfostr) + gboolean ignore_inode) { - g_auto(GStrv) maps = NULL; - guint i; + g_auto(GStrv) lines = NULL; g_assert (SYSPROF_IS_PROC_SOURCE (self)); g_assert (mapsstr != NULL); - g_assert (mountinfostr != NULL); g_assert (pid > 0); - sysprof_mountinfo_reset (self->mountinfo); - sysprof_mountinfo_parse_mountinfo (self->mountinfo, mountinfostr); + lines = g_strsplit (mapsstr, "\n", 0); - maps = g_strsplit (mapsstr, "\n", 0); - - for (i = 0; maps [i] != NULL; i++) + for (guint i = 0; lines[i] != NULL; i++) { - g_autofree gchar *translated = NULL; gchar file[512]; - const gchar *fileptr = file; gulong start; gulong end; gulong offset; gulong inode; gint r; - r = sscanf (maps [i], + r = sscanf (lines[i], "%lx-%lx %*15s %lx %*x:%*x %lu %512s", &start, &end, &offset, &inode, file); file [sizeof file - 1] = '\0'; @@ -122,7 +108,7 @@ sysprof_proc_source_populate_maps (SysprofProcSource *self, if (r != 5) continue; - if (strcmp ("[vdso]", file) == 0) + if (ignore_inode || strcmp ("[vdso]", file) == 0) { /* * Søren Sandmann Pedersen says: @@ -137,10 +123,6 @@ sysprof_proc_source_populate_maps (SysprofProcSource *self, inode = 0; } - /* Possibly translate the path based on mounts/mountinfo data */ - if ((translated = sysprof_mountinfo_translate (self->mountinfo, file))) - fileptr = translated; - sysprof_capture_writer_add_map (self->writer, SYSPROF_CAPTURE_CURRENT_TIME, -1, @@ -149,7 +131,7 @@ sysprof_proc_source_populate_maps (SysprofProcSource *self, end, offset, inode, - fileptr); + file); } } @@ -170,66 +152,42 @@ pid_is_interesting (SysprofProcSource *self, } static void -podman_info_free (gpointer data) +add_file (SysprofProcSource *self, + int pid, + const char *path, + const char *data) { - PodmanInfo *info = data; + gsize to_write = strlen (data); - g_free (info->id); - g_strfreev (info->layers); - g_free (info); + while (to_write > 0) + { + gsize this_write = MIN (to_write, 4096 * 2); + to_write -= this_write; + sysprof_capture_writer_add_file (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + pid, + path, + to_write == 0, + (const guint8 *)data, + this_write); + data += this_write; + } } static void -update_containers_json (SysprofProcSource *self) +sysprof_proc_source_populate_mountinfo (SysprofProcSource *self, + GPid pid, + const char *mountinfo) { - g_autoptr(JsonParser) parser = NULL; - g_autofree gchar *path = NULL; - JsonNode *root; - JsonArray *ar; - guint n_items; + g_autofree char *path = NULL; g_assert (SYSPROF_IS_PROC_SOURCE (self)); + g_assert (self->writer != NULL); + g_assert (mountinfo != NULL); - parser = json_parser_new (); - path = g_build_filename (g_get_user_data_dir (), - "containers", - "storage", - "overlay-containers", - "containers.json", - NULL); - - if (!json_parser_load_from_file (parser, path, NULL) || - !(root = json_parser_get_root (parser)) || - !JSON_NODE_HOLDS_ARRAY (root) || - !(ar = json_node_get_array (root))) - return; - - n_items = json_array_get_length (ar); - - for (guint i = 0; i < n_items; i++) - { - JsonObject *item = json_array_get_object_element (ar, i); - PodmanInfo *info; - const char *id; - const char *layer; - - if (item == NULL) - continue; - - id = json_object_get_string_member (item, "id"); - layer = json_object_get_string_member (item, "layer"); - - if (id == NULL || layer == NULL) - continue; - - info = g_new0 (PodmanInfo, 1); - info->id = g_strdup (id); - info->layers = g_new0 (char *, 2); - info->layers[0] = g_strdup (layer); - info->layers[1] = NULL; - - g_hash_table_insert (self->podman, info->id, info); - } + path = g_strdup_printf ("/proc/%d/mountinfo", pid); + add_file (self, pid, path, mountinfo); } static void @@ -237,84 +195,37 @@ sysprof_proc_source_populate_pid_podman (SysprofProcSource *self, GPid pid, const char *container) { - PodmanInfo *info; + g_auto(GStrv) layers = NULL; g_assert (SYSPROF_IS_PROC_SOURCE (self)); g_assert (container != NULL); - if (!(info = g_hash_table_lookup (self->podman, container))) - { - update_containers_json (self); - info = g_hash_table_lookup (self->podman, container); - } + if (!(layers = sysprof_podman_get_layers (self->podman, container))) + return; - if (info != NULL) - { - for (guint i = 0; info->layers[i]; i++) - { - g_autofree char *path = g_build_filename (g_get_user_data_dir (), - "containers", - "storage", - "overlay", - info->layers[i], - "diff", - NULL); - sysprof_capture_writer_add_overlay (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, pid, i, path, "/"); - } - } + for (guint i = 0; layers[i]; i++) + sysprof_capture_writer_add_overlay (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, pid, i, layers[i], "/"); } static void -sysprof_proc_source_populate_pid_flatpak (SysprofProcSource *self, - GPid pid, - const char *app_id) -{ - g_autofree gchar *info_path = NULL; - g_autoptr(GKeyFile) key_file = NULL; - g_autofree gchar *app_path = NULL; - g_autofree gchar *runtime_path = NULL; - g_autofree gchar *contents = NULL; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (app_id != NULL); - - info_path = g_strdup_printf ("/proc/%d/root/.flatpak-info", pid); - key_file = g_key_file_new (); - - if (!sysprof_helpers_get_proc_file (sysprof_helpers_get_default (), - info_path, NULL, &contents, NULL)) - return; - - if (!g_key_file_load_from_data (key_file, contents, -1, 0, NULL) || - !g_key_file_has_group (key_file, "Instance") || - !(app_path = g_key_file_get_string (key_file, "Instance", "app-path", NULL)) || - !(runtime_path = g_key_file_get_string (key_file, "Instance", "runtime-path", NULL))) - return; - - sysprof_capture_writer_add_overlay (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, pid, 0, runtime_path, "/usr"); - sysprof_capture_writer_add_overlay (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, pid, 0, app_path, "/app"); -} - -static void -sysprof_proc_source_populate_pid_root (SysprofProcSource *self, +sysprof_proc_source_populate_overlays (SysprofProcSource *self, GPid pid, const char *cgroup) { static GRegex *flatpak; static GRegex *podman; - g_autoptr(GMatchInfo) podman_match = NULL; g_autoptr(GMatchInfo) flatpak_match = NULL; + g_autoptr(GMatchInfo) podman_match = NULL; g_assert (SYSPROF_IS_PROC_SOURCE (self)); g_assert (cgroup != NULL); + if (strcmp (cgroup, "") == 0) + return; + /* This function tries to discover the podman container that contains the * process identified on the host as @pid. We can only do anything with this * if the pids are in containers that the running user controls (so that we @@ -327,18 +238,12 @@ sysprof_proc_source_populate_pid_root (SysprofProcSource *self, * * -- Christian */ - if (podman == NULL) + if G_UNLIKELY (podman == NULL) { podman = g_regex_new ("libpod-([a-z0-9]{64})\\.scope", G_REGEX_OPTIMIZE, 0, NULL); g_assert (podman != NULL); } - /* If this looks like a cgroup associated with a Flatpak, then we can find - * information about the filesystem in /proc/$pid/root/.flatpak-info (assuming - * we can actually read that file. That is possible from the host, but not - * really if we are running in a Flatpak ourself, so we access it through - * the daemon to ensure we can access it. - */ if (flatpak == NULL) { flatpak = g_regex_new ("app-flatpak-([a-zA-Z_\\-\\.]+)-[0-9]+\\.scope", G_REGEX_OPTIMIZE, 0, NULL); @@ -347,15 +252,25 @@ sysprof_proc_source_populate_pid_root (SysprofProcSource *self, if (g_regex_match (podman, cgroup, 0, &podman_match)) { - char *word = g_match_info_fetch (podman_match, 1); + SysprofHelpers *helpers = sysprof_helpers_get_default (); + g_autofree char *word = g_match_info_fetch (podman_match, 1); + g_autofree char *path = g_strdup_printf ("/proc/%d/root/run/.containerenv", pid); + g_autofree char *info = NULL; + sysprof_proc_source_populate_pid_podman (self, pid, word); - g_free (word); + + if (sysprof_helpers_get_proc_file (helpers, path, NULL, &info, NULL)) + add_file (self, pid, "/run/.containerenv", info); } else if (g_regex_match (flatpak, cgroup, 0, &flatpak_match)) { - char *word = g_match_info_fetch (flatpak_match, 1); - sysprof_proc_source_populate_pid_flatpak (self, pid, word); - g_free (word); + SysprofHelpers *helpers = sysprof_helpers_get_default (); + g_autofree char *word = g_match_info_fetch (flatpak_match, 1); + g_autofree char *path = g_strdup_printf ("/proc/%d/root/.flatpak-info", pid); + g_autofree char *info = NULL; + + if (sysprof_helpers_get_proc_file (helpers, path, NULL, &info, NULL)) + add_file (self, pid, "/.flatpak-info", info); } } @@ -374,11 +289,14 @@ sysprof_proc_source_populate (SysprofProcSource *self, if (self->writer == NULL) return; + if (self->podman == NULL) + self->podman = sysprof_podman_snapshot_current_user (); + helpers = sysprof_helpers_get_default (); if (!sysprof_helpers_get_proc_file (helpers, "/proc/mounts", NULL, &mounts, NULL)) return; - sysprof_mountinfo_parse_mounts (self->mountinfo, mounts); + add_file (self, -1, "/proc/mounts", mounts); n_pids = g_variant_n_children (info); for (gsize i = 0; i < n_pids; i++) @@ -390,6 +308,7 @@ sysprof_proc_source_populate (SysprofProcSource *self, const gchar *mountinfo; const gchar *maps; const gchar *cgroup; + gboolean ignore_inode; gint32 pid; g_variant_dict_init (&dict, pidinfo); @@ -413,9 +332,15 @@ sysprof_proc_source_populate (SysprofProcSource *self, if (!g_variant_dict_lookup (&dict, "cgroup", "&s", &cgroup)) cgroup = ""; + /* Ignore inodes from podman/toolbox because they appear + * to always be wrong. We'll have to rely on CRC instead. + */ + ignore_inode = strstr (cgroup, "/libpod-") != NULL; + sysprof_proc_source_populate_process (self, pid, *cmdline ? cmdline : comm); - sysprof_proc_source_populate_maps (self, pid, maps, mountinfo); - sysprof_proc_source_populate_pid_root (self, pid, cgroup); + sysprof_proc_source_populate_mountinfo (self, pid, mountinfo); + sysprof_proc_source_populate_maps (self, pid, maps, ignore_inode); + sysprof_proc_source_populate_overlays (self, pid, cgroup); skip: g_variant_dict_clear (&dict); @@ -564,8 +489,7 @@ sysprof_proc_source_finalize (GObject *object) g_clear_pointer (&self->writer, sysprof_capture_writer_unref); g_clear_pointer (&self->pids, g_array_unref); - g_clear_pointer (&self->mountinfo, sysprof_mountinfo_free); - g_clear_pointer (&self->podman, g_hash_table_unref); + g_clear_pointer (&self->podman, sysprof_podman_free); G_OBJECT_CLASS (sysprof_proc_source_parent_class)->finalize (object); } @@ -582,8 +506,6 @@ static void sysprof_proc_source_init (SysprofProcSource *self) { self->pids = g_array_new (FALSE, FALSE, sizeof (GPid)); - self->mountinfo = sysprof_mountinfo_new (); - self->podman = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, podman_info_free); } SysprofSource * diff --git a/src/libsysprof/sysprof-symbol-resolver-private.h b/src/libsysprof/sysprof-symbol-resolver-private.h new file mode 100644 index 00000000..13039463 --- /dev/null +++ b/src/libsysprof/sysprof-symbol-resolver-private.h @@ -0,0 +1,32 @@ +/* sysprof-symbol-resolver-private.h + * + * Copyright 2021 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 +#include + +G_BEGIN_DECLS + +char *_sysprof_symbol_resolver_load_file (SysprofCaptureReader *reader, + const char *path); +char **_sysprof_symbol_resolver_guess_debug_dirs (const char *path); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-symbol-resolver.c b/src/libsysprof/sysprof-symbol-resolver.c index 83da30a0..626a904b 100644 --- a/src/libsysprof/sysprof-symbol-resolver.c +++ b/src/libsysprof/sysprof-symbol-resolver.c @@ -20,6 +20,7 @@ #include "config.h" +#include "sysprof-platform.h" #include "sysprof-symbol-resolver.h" G_DEFINE_INTERFACE (SysprofSymbolResolver, sysprof_symbol_resolver, G_TYPE_OBJECT) @@ -125,11 +126,11 @@ sysprof_symbol_resolver_resolve (SysprofSymbolResolver *self, */ gchar * sysprof_symbol_resolver_resolve_with_context (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - GQuark *tag) + guint64 time, + GPid pid, + SysprofAddressContext context, + SysprofCaptureAddress address, + GQuark *tag) { GQuark dummy; @@ -142,3 +143,54 @@ sysprof_symbol_resolver_resolve_with_context (SysprofSymbolResolver *self, return SYSPROF_SYMBOL_RESOLVER_GET_IFACE (self)->resolve_with_context (self, time, pid, context, address, tag); } + +char * +_sysprof_symbol_resolver_load_file (SysprofCaptureReader *reader, + const char *path) +{ + g_autofree char *data = NULL; + goffset len; + goffset pos = 0; + int fd; + + g_assert (reader != NULL); + g_assert (path != NULL); + + sysprof_capture_reader_reset (reader); + + if (-1 == (fd = sysprof_memfd_create ("")) || + !sysprof_capture_reader_read_file_fd (reader, path, fd)) + { + if (fd != -1) + close (fd); + return NULL; + } + + len = lseek (fd, 0L, SEEK_CUR); + data = g_malloc (len + 1); + lseek (fd, 0L, SEEK_SET); + + while (pos < len) + { + gssize n_read = read (fd, data + pos, len - pos); + + if (n_read < 0) + return NULL; + + pos += n_read; + } + + data[len] = 0; + close (fd); + + return g_steal_pointer (&data); +} + +char ** +_sysprof_symbol_resolver_guess_debug_dirs (const char *path) +{ + if (path == NULL) + return NULL; + + return NULL; +} diff --git a/src/tests/list-all-maps.c b/src/tests/list-all-maps.c new file mode 100644 index 00000000..e53ea6b9 --- /dev/null +++ b/src/tests/list-all-maps.c @@ -0,0 +1,358 @@ +/* list-all-maps.c + * + * Copyright 2021 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 + +#include "../libsysprof/sysprof-helpers.h" +#include "../libsysprof/sysprof-mountinfo.h" +#include "../libsysprof/sysprof-path-resolver.h" +#include "../libsysprof/sysprof-podman.h" + +typedef enum { + PROCESS_KIND_HOST, + PROCESS_KIND_FLATPAK, + PROCESS_KIND_PODMAN, +} ProcessKind; + +typedef struct +{ + ProcessKind kind : 2; + int pid : 29; + char *kind_identifier; + char *mountinfo; + char *comm; + GArray *maps; + GArray *overlays; +} ProcessInfo; + +typedef struct +{ + char *on_host; + char *in_process; +} ProcessOverlay; + +typedef struct +{ + char *file; + SysprofCaptureAddress start; + SysprofCaptureAddress end; + SysprofCaptureAddress offset; + ino_t inode; +} ProcessMap; + +static const char * +kind_to_string (ProcessKind kind) +{ + switch (kind) + { + case PROCESS_KIND_HOST: + return "Host"; + case PROCESS_KIND_PODMAN: + return "Podman"; + case PROCESS_KIND_FLATPAK: + return "Flatpak"; + default: + return "Unknown"; + } +} + +static void +process_overlay_clear (ProcessOverlay *overlay) +{ + g_free (overlay->on_host); + g_free (overlay->in_process); +} + +static void +process_info_clear (ProcessInfo *info) +{ + g_free (info->kind_identifier); + g_free (info->mountinfo); + g_free (info->comm); + g_clear_pointer (&info->maps, g_array_unref); + g_clear_pointer (&info->overlays, g_array_unref); +} + +static void +process_map_clear (ProcessMap *map) +{ + g_free (map->file); +} + +static ProcessKind +get_process_kind_from_cgroup (int pid, + const char *cgroup, + char **identifier) +{ + static GRegex *podman_regex; + static GRegex *flatpak_regex; + + g_autoptr(GMatchInfo) podman_match = NULL; + g_autoptr(GMatchInfo) flatpak_match = NULL; + + if G_UNLIKELY (podman_regex == NULL) + podman_regex = g_regex_new ("libpod-([a-z0-9]{64})\\.scope", G_REGEX_OPTIMIZE, 0, NULL); + + if G_UNLIKELY (flatpak_regex == NULL) + flatpak_regex = g_regex_new ("app-flatpak-([a-zA-Z_\\-\\.]+)-[0-9]+\\.scope", G_REGEX_OPTIMIZE, 0, NULL); + + if (g_regex_match (podman_regex, cgroup, 0, &podman_match)) + { + if (identifier != NULL) + *identifier = g_match_info_fetch (podman_match, 1); + return PROCESS_KIND_PODMAN; + } + else if (g_regex_match (flatpak_regex, cgroup, 0, &flatpak_match)) + { + if (identifier != NULL) + *identifier = g_match_info_fetch (flatpak_match, 1); + return PROCESS_KIND_FLATPAK; + } + else + { + if (identifier != NULL) + *identifier = NULL; + return PROCESS_KIND_HOST; + } +} + +static void +process_info_populate_podman_overlays (ProcessInfo *proc, + SysprofPodman *podman) +{ + g_auto(GStrv) layers = NULL; + + g_assert (proc != NULL); + g_assert (proc->kind == PROCESS_KIND_PODMAN); + + if ((layers = sysprof_podman_get_layers (podman, proc->kind_identifier))) + { + for (guint i = 0; layers[i]; i++) + { + ProcessOverlay overlay; + g_autofree char *path = g_build_filename (g_get_user_data_dir (), + "containers", + "storage", + "overlay", + layers[i], + "diff", + NULL); + + /* XXX: this really only supports one layer */ + overlay.in_process = g_strdup ("/"); + overlay.on_host = g_steal_pointer (&path); + + if (proc->overlays == NULL) + { + proc->overlays = g_array_new (FALSE, FALSE, sizeof (ProcessOverlay)); + g_array_set_clear_func (proc->overlays, (GDestroyNotify)process_overlay_clear); + } + + g_array_append_val (proc->overlays, overlay); + } + } +} + +static void +process_info_populate_maps (ProcessInfo *proc, + const char *maps) +{ + g_auto(GStrv) lines = NULL; + + g_assert (proc != NULL); + g_assert (maps != NULL); + + if (proc->maps == NULL) + { + proc->maps = g_array_new (FALSE, FALSE, sizeof (ProcessMap)); + g_array_set_clear_func (proc->maps, (GDestroyNotify)process_map_clear); + } + + lines = g_strsplit (maps, "\n", 0); + + for (guint i = 0; lines[i] != NULL; i++) + { + ProcessMap map; + gchar file[512]; + gulong start; + gulong end; + gulong offset; + gulong inode; + int r; + + r = sscanf (lines[i], + "%lx-%lx %*15s %lx %*x:%*x %lu %512s", + &start, &end, &offset, &inode, file); + file[sizeof file - 1] = '\0'; + + if (r != 5) + continue; + + if (strcmp ("[vdso]", file) == 0) + { + /* + * Søren Sandmann Pedersen says: + * + * For the vdso, the kernel reports 'offset' as the + * the same as the mapping address. This doesn't make + * any sense to me, so we just zero it here. There + * is code in binfile.c (read_inode) that returns 0 + * for [vdso]. + */ + offset = 0; + inode = 0; + } + + map.file = g_strdup (file); + map.start = start; + map.end = end; + map.offset = offset; + map.inode = inode; + + g_array_append_val (proc->maps, map); + } +} + +static gboolean +process_info_populate (ProcessInfo *proc, + GVariant *variant, + SysprofPodman *podman) +{ + const char *str; + int pid; + + g_assert (proc != NULL); + g_assert (proc->pid == 0); + g_assert (variant != NULL); + g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT)); + + if (g_variant_lookup (variant, "pid", "i", &pid)) + proc->pid = pid; + else + return FALSE; + + if (g_variant_lookup (variant, "comm", "&s", &str)) + proc->comm = g_strdup (str); + + if (g_variant_lookup (variant, "cgroup", "&s", &str)) + proc->kind = get_process_kind_from_cgroup (pid, str, &proc->kind_identifier); + else + proc->kind = PROCESS_KIND_HOST; + + if (proc->kind == PROCESS_KIND_PODMAN) + process_info_populate_podman_overlays (proc, podman); + + if (g_variant_lookup (variant, "mountinfo", "&s", &str)) + proc->mountinfo = g_strdup (str); + + if (g_variant_lookup (variant, "maps", "&s", &str)) + process_info_populate_maps (proc, str); + + return TRUE; +} + +static gboolean +list_all_maps (GError **error) +{ + SysprofHelpers *helpers = sysprof_helpers_get_default (); + g_autoptr(SysprofPodman) podman = NULL; + g_autofree char *mounts = NULL; + g_autoptr(GVariant) info = NULL; + g_autoptr(GArray) processes = NULL; + GVariant *value; + GVariantIter iter; + + if (!sysprof_helpers_get_process_info (helpers, "pid,maps,mountinfo,cmdline,comm,cgroup", FALSE, NULL, &info, error)) + return FALSE; + + if (!sysprof_helpers_get_proc_file (helpers, "/proc/mounts", NULL, &mounts, error)) + return FALSE; + + podman = sysprof_podman_snapshot_current_user (); + + processes = g_array_new (FALSE, TRUE, sizeof (ProcessInfo)); + g_array_set_clear_func (processes, (GDestroyNotify)process_info_clear); + + g_variant_iter_init (&iter, info); + while (g_variant_iter_loop (&iter, "@a{sv}", &value)) + { + ProcessInfo *proc; + + g_array_set_size (processes, processes->len + 1); + proc = &g_array_index (processes, ProcessInfo, processes->len - 1); + + if (!process_info_populate (proc, value, podman)) + g_array_set_size (processes, processes->len - 1); + } + + for (guint i = 0; i < processes->len; i++) + { + const ProcessInfo *proc = &g_array_index (processes, ProcessInfo, i); + g_autoptr(SysprofPathResolver) resolver = _sysprof_path_resolver_new (mounts, proc->mountinfo); + + if (proc->maps == NULL || proc->maps->len == 0) + continue; + + g_print ("Process %d", proc->pid); + if (proc->kind != PROCESS_KIND_HOST) + g_print (" (%s)", kind_to_string (proc->kind)); + g_print (" %s\n", proc->comm); + + if (proc->overlays != NULL) + { + for (guint j = 0; j < proc->overlays->len; j++) + { + const ProcessOverlay *overlay = &g_array_index (proc->overlays, ProcessOverlay, j); + _sysprof_path_resolver_add_overlay (resolver, overlay->in_process, overlay->on_host, j); + } + } + + for (guint j = 0; j < proc->maps->len; j++) + { + const ProcessMap *map = &g_array_index (proc->maps, ProcessMap, j); + g_autofree char *path = _sysprof_path_resolver_resolve (resolver, map->file); + + if (path != NULL) + { + g_print (" %s", path); + if (!g_file_test (path, G_FILE_TEST_EXISTS)) + g_print (" (missing)"); + g_print ("\n"); + } + } + + g_print ("\n"); + } + + return TRUE; +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GError) error = NULL; + if (!list_all_maps (&error)) + g_printerr ("%s\n", error->message); + return error ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/tests/list-maps.c b/src/tests/list-maps.c new file mode 100644 index 00000000..dbdf5045 --- /dev/null +++ b/src/tests/list-maps.c @@ -0,0 +1,103 @@ +/* list-maps.c + * + * Copyright 2021 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 +#include +#include + +#include "sysprof-elf-symbol-resolver-private.h" + +static ino_t +read_inode (const char *filename) +{ + struct stat statbuf; + + if (filename == NULL) + return (ino_t)-1; + + if (strcmp (filename, "[vdso]") == 0) + return (ino_t)0; + + if (stat (filename, &statbuf) < 0) + return (ino_t)-1; + + return statbuf.st_ino; +} + +static void +list_maps (const char *filename) +{ + g_autoptr(SysprofCaptureReader) reader = NULL; + g_autoptr(SysprofSymbolResolver) resolver = NULL; + SysprofCaptureFrameType type; + + if (!(reader = sysprof_capture_reader_new (filename))) + return; + + resolver = sysprof_elf_symbol_resolver_new (); + sysprof_symbol_resolver_load (resolver, reader); + sysprof_capture_reader_reset (reader); + + while (sysprof_capture_reader_peek_type (reader, &type)) + { + if (type == SYSPROF_CAPTURE_FRAME_MAP) + { + const SysprofCaptureMap *ev = sysprof_capture_reader_read_map (reader); + g_autofree char *resolved = _sysprof_elf_symbol_resolver_resolve_path (SYSPROF_ELF_SYMBOL_RESOLVER (resolver), ev->frame.pid, ev->filename); + const char *kind = _sysprof_elf_symbol_resolver_get_pid_kind (SYSPROF_ELF_SYMBOL_RESOLVER (resolver), ev->frame.pid); + ino_t inode = read_inode (resolved ? resolved : ev->filename); + + g_print ("PID %u (%s): ", ev->frame.pid, kind); + g_print ("%s => %s", ev->filename, resolved ? resolved : ev->filename); + + if (inode == (ino_t)-1) + g_print (" (missing)"); + else if (ev->inode != 0 && inode != ev->inode) + g_print (" (Inode mismatch, expected %lu got %lu)", + (gulong)ev->inode, (gulong)inode); + + g_print ("\n"); + } + else + { + if (!sysprof_capture_reader_skip (reader)) + break; + } + } +} + +int +main (int argc, + char *argv[]) +{ + if (argc == 1) + { + g_printerr ("usage: %s CAPTURE_FILE...\n", argv[0]); + return 0; + } + + for (guint i = 1; i < argc; i++) + list_maps (argv[i]); + + return 0; +} diff --git a/src/tests/memory-stack-stash.c b/src/tests/memory-stack-stash.c index 5078071d..f5dea02f 100644 --- a/src/tests/memory-stack-stash.c +++ b/src/tests/memory-stack-stash.c @@ -28,7 +28,6 @@ #include #include "../stackstash.h" -#include "../stackstash.c" static void memory_stack_stash (SysprofCaptureReader *reader) diff --git a/src/tests/meson.build b/src/tests/meson.build index 6519fb79..44ae8eee 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -39,6 +39,7 @@ find_temp_allocs = executable('find-temp-allocs', 'find-temp-allocs.c', dependencies: test_capture_deps, ) + test('test-capture', test_capture, env: test_env) test('test-capture-cursor', test_capture_cursor, env: test_env) test('test-mapped-ring-buffer', test_mapped_ring_buffer, env: test_env) @@ -46,13 +47,10 @@ test('test-mapped-ring-buffer', test_mapped_ring_buffer, env: test_env) if get_option('libsysprof') test_deps = [ - libsysprof_dep, + libsysprof_static_dep, ] -test_addr_map = executable('test-addr-map', - ['test-addr-map.c', - '../libsysprof/sysprof-map-lookaside.c', - '../libsysprof/sysprof-symbol-map.c'], +test_addr_map = executable('test-addr-map', 'test-addr-map.c', c_args: test_cflags, dependencies: test_deps, ) @@ -62,55 +60,61 @@ test_addr_decode = executable('test-addr-decode', 'test-addr-decode.c', dependencies: test_deps, ) -test_mountinfo = executable('test-mountinfo', - [ 'test-mountinfo.c', - '../libsysprof/sysprof-mountinfo.c'], +test_mountinfo = executable('test-mountinfo', 'test-mountinfo.c', c_args: test_cflags, dependencies: test_deps, ) -test_flatpak = executable('test-flatpak', - [ 'test-flatpak.c', - '../libsysprof/sysprof-flatpak.c'], +test_flatpak = executable('test-flatpak', 'test-flatpak.c', c_args: test_cflags, dependencies: test_deps, ) -test_resolvers = executable('test-resolvers', - [ 'test-resolvers.c' ], +test_resolvers = executable('test-resolvers', 'test-resolvers.c', c_args: test_cflags, dependencies: test_deps, ) -allocs_by_size = executable('allocs-by-size', - ['allocs-by-size.c'], +allocs_by_size = executable('allocs-by-size', 'allocs-by-size.c', c_args: test_cflags, dependencies: test_deps, ) -allocs_within_mark = executable('allocs-within-mark', - ['allocs-within-mark.c'], +allocs_within_mark = executable('allocs-within-mark', 'allocs-within-mark.c', c_args: test_cflags, dependencies: test_deps, ) -cross_thread_frees = executable('cross-thread-frees', - ['cross-thread-frees.c'], +cross_thread_frees = executable('cross-thread-frees', 'cross-thread-frees.c', c_args: test_cflags, dependencies: test_deps, ) -memory_stack_stash = executable('memory-stack-stash', - ['memory-stack-stash.c'], +memory_stack_stash = executable('memory-stack-stash', 'memory-stack-stash.c', c_args: test_cflags, dependencies: test_deps, ) -show_page_usage = executable('show-page-usage', - [ 'show-page-usage.c' ], +read_build_id = executable('read-build-id', 'read-build-id.c', c_args: test_cflags, - dependencies: test_deps + [ librax_dep, - dependency('cairo') ], + dependencies: test_deps, +) + +show_page_usage = executable('show-page-usage', 'show-page-usage.c', + c_args: test_cflags, + dependencies: test_deps + [dependency('cairo')], +) + +list_pid_maps = executable('list-all-maps', 'list-all-maps.c', + c_args: test_cflags, + dependencies: [libsysprof_static_dep], + include_directories: include_directories('..'), +) + +list_maps = executable('list-maps', 'list-maps.c', + c_args: test_cflags, + dependencies: [libsysprof_static_dep], + include_directories: include_directories('..'), ) if get_option('enable_gtk') diff --git a/src/tests/read-build-id.c b/src/tests/read-build-id.c new file mode 100644 index 00000000..9f9a7ab0 --- /dev/null +++ b/src/tests/read-build-id.c @@ -0,0 +1,57 @@ +/* read-build-id.c + * + * Copyright 2021 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 "elfparser.h" + +int +main (int argc, + char *argv[]) +{ + for (guint i = 1; i < argc; i++) + { + g_autoptr(GError) error = NULL; + const char *filename = argv[i]; + ElfParser *parser = elf_parser_new (filename, &error); + const char *build_id; + const char *debug_link; + guint crc; + + if (parser == NULL) + { + if (error != NULL) + g_printerr ("%s: %s\n", filename, error->message); + else + g_printerr ("%s: Failed to load as ELF\n", filename); + g_clear_error (&error); + continue; + } + + build_id = elf_parser_get_build_id (parser); + debug_link = elf_parser_get_debug_link (parser, &crc); + + g_print ("%s: %s (%s)\n", filename, build_id, debug_link); + + elf_parser_free (parser); + } + + return 0; +} diff --git a/src/tests/test-addr-decode.c b/src/tests/test-addr-decode.c index db1b96b3..bfe7db55 100644 --- a/src/tests/test-addr-decode.c +++ b/src/tests/test-addr-decode.c @@ -60,10 +60,15 @@ main (gint argc, sample->addrs[i], &tag); - g_print ("%u: %s [%s]\n", - i, - name ? name : "-- missing --", - tag ? g_quark_to_string (tag) : "No Tag"); + if (tag) + g_print ("%u: %s [%s]\n", + i, + name ? name : "-- missing --", + g_quark_to_string (tag)); + else + g_print ("%u: %s\n", + i, + name ? name : "-- missing --"); } g_print ("======\n"); diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c new file mode 100644 index 00000000..4b27b70d --- /dev/null +++ b/src/tests/test-utils.c @@ -0,0 +1,49 @@ +/* test-utils.c + * + * Copyright 2021 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 + +static void +test_debug_dirs (void) +{ + g_auto(GStrv) test1 = NULL; + g_auto(GStrv) test2 = NULL; + + test1 = _sysprof_symbol_resolver_guess_debug_dirs ("/usr/bin/foo"); + g_assert_nonnull (test1); + g_assert_cmpstr (test1[0], ==, "/usr/lib/debug/bin"); + g_assert_cmpstr (test1[1], ==, "/usr/lib64/debug/bin"); + g_assert_cmpstr (test1[2], ==, NULL); + + test2 = _sysprof_symbol_resolver_guess_debug_dirs ("/usr/lib64/libc.so.6"); + g_assert_nonnull (test2); + g_assert_cmpstr (test2[0], ==, "/usr/lib/debug/lib64"); + g_assert_cmpstr (test2[1], ==, "/usr/lib64/debug/lib64"); + g_assert_cmpstr (test2[2], ==, NULL); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/Sysprof/SymbolResolver/guess_debug_dirs", test_debug_dirs); + return g_test_run (); +}