Files
sysprof/src/libsysprof/sysprof-elf-symbol-resolver.c
Christian Hergert 1ef9fc1e30 elf: handle NULL lookaside
This code uses the hashtable directly to avoid the overhead of calling
the path which creates ProcessInfo entries. So we need to also handle
the chance the lookaside is NULL.
2022-09-05 20:51:58 -07:00

687 lines
23 KiB
C

/* sysprof-elf-symbol-resolver.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include "binfile.h"
#include "elfparser.h"
#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;
GHashTable *processes;
GStringChunk *chunks;
GHashTable *bin_files;
GHashTable *tag_cache;
};
static void symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface);
G_DEFINE_TYPE_EXTENDED (SysprofElfSymbolResolver,
sysprof_elf_symbol_resolver,
G_TYPE_OBJECT,
0,
G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SYMBOL_RESOLVER,
symbol_resolver_iface_init))
static void
process_info_free (gpointer data)
{
ProcessInfo *pi = data;
if (pi != NULL)
{
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);
}
}
static const char * const *
process_info_get_debug_dirs (const ProcessInfo *pi)
{
static const char *standard[] = { "/usr/lib/debug", NULL };
if (pi->debug_dirs)
return (const char * const *) pi->debug_dirs;
return standard;
}
static void
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->tag_cache, g_hash_table_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);
}
static void
sysprof_elf_symbol_resolver_class_init (SysprofElfSymbolResolverClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sysprof_elf_symbol_resolver_finalize;
}
static void
sysprof_elf_symbol_resolver_init (SysprofElfSymbolResolver *self)
{
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_ELF_SYMBOL_RESOLVER (self));
g_assert (reader != NULL);
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);
g_autofree gchar *branch = g_key_file_get_string (keyfile, "Instance", "branch", NULL);
g_autofree gchar *arch = g_key_file_get_string (keyfile, "Instance", "arch", NULL);
g_autofree gchar *app_name = g_key_file_get_string (keyfile, "Application", "name", NULL);
g_autofree gchar *manifest_dir = g_path_get_dirname (app_path);
g_autofree gchar *manifest_path = g_build_filename (manifest_dir, "metadata", NULL);
g_autoptr(GKeyFile) manifest = g_key_file_new ();
GPtrArray *dirs = g_ptr_array_new ();
/* TODO: extensions */
g_ptr_array_add (dirs, g_build_filename (app_path, "lib", "debug", NULL));
g_ptr_array_add (dirs, g_build_filename (runtime_path, "lib", "debug", NULL));
/* Try to figure out flatpak runtime debug symbol paths. */
if (g_key_file_load_from_file (manifest, manifest_path, 0, NULL))
{
/* Add the SDK debug extension. */
g_autofree gchar *sdk = g_key_file_get_string (manifest, "Application", "sdk", NULL);
if (sdk)
{
/* Go from a string like "org.gnome.Sdk/x86_64/41" to "org.gnome.Sdk.Debug/x86_64/41". */
g_autoptr(GString) debug = g_string_new (sdk);
g_string_replace (debug, "/", ".Debug/", 1);
/* Construct a path like "/var/lib/flatpak/runtime/org.gnome.Sdk.Debug/x86_64/41/active/files". */
g_ptr_array_add (dirs, g_build_filename ("/var/lib/flatpak/runtime", debug->str, "active/files", NULL));
}
/* Add the app's debug extension. */
if (app_name && branch && arch)
{
/* Go from a string like "org.gnome.TextEditor" to "org.gnome.TextEditor.Debug". */
g_autoptr(GString) debug = g_string_new (app_name);
g_string_append (debug, ".Debug");
/* Construct a path like "/var/lib/flatpak/runtime/org.gnome.TextEditor.Debug/x86_64/master/active/files". */
g_ptr_array_add (dirs, g_build_filename ("/var/lib/flatpak/runtime", debug->str, arch, branch, "active/files", NULL));
}
}
g_ptr_array_add (dirs, 0);
pi->debug_dirs = (gchar**) g_ptr_array_free (dirs, FALSE);
}
}
}
}
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);
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 (pi->lookaside == NULL)
pi->lookaside = sysprof_map_lookaside_new ();
sysprof_map_lookaside_insert (pi->lookaside, &map);
}
else
{
if (!sysprof_capture_reader_skip (reader))
return;
}
}
}
static bin_file_t *
sysprof_elf_symbol_resolver_get_bin_file (SysprofElfSymbolResolver *self,
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));
if ((bin_file = g_hash_table_lookup (self->bin_files, filename)))
return 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;
}
static GQuark
guess_tag (SysprofElfSymbolResolver *self,
const SysprofMap *map)
{
g_assert (map != NULL);
g_assert (map->filename != NULL);
if (!g_hash_table_contains (self->tag_cache, map->filename))
{
GQuark tag = 0;
if (strstr (map->filename, "/libgobject-2.0."))
tag = g_quark_from_static_string ("GObject");
else if (strstr (map->filename, "/libc.so.6"))
tag = g_quark_from_static_string ("libc");
else if (strstr (map->filename, "/libstdc++.so.6"))
tag = g_quark_from_static_string ("stdc++");
else if (strstr (map->filename, "/libglib-2.0."))
tag = g_quark_from_static_string ("GLib");
else if (strstr (map->filename, "/libgio-2.0."))
tag = g_quark_from_static_string ("Gio");
else if (strstr (map->filename, "/libgirepository-1.0."))
tag = g_quark_from_static_string ("Introspection");
else if (strstr (map->filename, "/libgtk-4."))
tag = g_quark_from_static_string ("Gtk 4");
else if (strstr (map->filename, "/libgtk-3."))
tag = g_quark_from_static_string ("Gtk 3");
else if (strstr (map->filename, "/libgdk-3."))
tag = g_quark_from_static_string ("Gdk 3");
else if (strstr (map->filename, "/libgtksourceview-3.0"))
tag = g_quark_from_static_string ("GtkSourceView-3");
else if (strstr (map->filename, "/libgtksourceview-4"))
tag = g_quark_from_static_string ("GtkSourceView-4");
else if (strstr (map->filename, "/libpixman-1"))
tag = g_quark_from_static_string ("Pixman");
else if (strstr (map->filename, "/libcairo."))
tag = g_quark_from_static_string ("cairo");
else if (strstr (map->filename, "/libgstreamer-1."))
tag = g_quark_from_static_string ("GStreamer");
else if (strstr (map->filename, "/libX11."))
tag = g_quark_from_static_string ("X11");
else if (strstr (map->filename, "/libpango-1.0."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libpangocairo-1.0."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libpangomm-1.4."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libpangoft2-1.0"))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libpangoxft-1.0."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libclutter-"))
tag = g_quark_from_static_string ("Clutter");
else if (strstr (map->filename, "/libcogl.") ||
strstr (map->filename, "/libcogl-"))
tag = g_quark_from_static_string ("Cogl");
else if (strstr (map->filename, "/libffi."))
tag = g_quark_from_static_string ("libffi");
else if (strstr (map->filename, "/libwayland-"))
tag = g_quark_from_static_string ("Wayland");
else if (strstr (map->filename, "/libinput."))
tag = g_quark_from_static_string ("libinput");
else if (strstr (map->filename, "/libgjs."))
tag = g_quark_from_static_string ("Gjs");
else if (strstr (map->filename, "/libmozjs-"))
tag = g_quark_from_static_string ("MozJS");
else if (strstr (map->filename, "/libGL."))
tag = g_quark_from_static_string ("GL");
else if (strstr (map->filename, "/libEGL."))
tag = g_quark_from_static_string ("EGL");
g_hash_table_insert (self->tag_cache,
g_strdup (map->filename),
GSIZE_TO_POINTER (tag));
}
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tag_cache, map->filename));
}
gboolean
sysprof_elf_symbol_resolver_resolve_full (SysprofElfSymbolResolver *self,
guint64 time,
GPid pid,
SysprofAddressContext context,
SysprofCaptureAddress address,
SysprofCaptureAddress *begin,
SysprofCaptureAddress *end,
gchar **name,
GQuark *tag)
{
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;
g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self));
g_assert (name != NULL);
g_assert (begin != NULL);
g_assert (end != NULL);
*name = NULL;
if (context != SYSPROF_ADDRESS_CONTEXT_USER)
return FALSE;
if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid))))
return FALSE;
if (pi->lookaside == NULL)
return FALSE;
if (!(map = sysprof_map_lookaside_lookup (pi->lookaside, address)))
return FALSE;
address -= map->start;
address += map->offset;
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);
return TRUE;
}
bin_sym = bin_file_lookup_symbol (bin_file, address);
bin_sym_name = bin_symbol_get_name (bin_file, bin_sym);
if G_LIKELY (map->filename)
*tag = guess_tag (self, map);
*name = elf_demangle (bin_sym_name);
bin_symbol_get_address_range (bin_file, bin_sym, &ubegin, &uend);
*begin = ubegin;
*end = uend;
return TRUE;
}
static gchar *
sysprof_elf_symbol_resolver_resolve_with_context (SysprofSymbolResolver *resolver,
guint64 time,
GPid pid,
SysprofAddressContext context,
SysprofCaptureAddress address,
GQuark *tag)
{
gchar *name = NULL;
SysprofCaptureAddress begin, end;
/* If not user context, nothing we can do here */
if (context != SYSPROF_ADDRESS_CONTEXT_USER)
return NULL;
/* If this is a jitmap entry, bail early to save some cycles */
if ((address & SYSPROF_CAPTURE_JITMAP_MARK) == SYSPROF_CAPTURE_JITMAP_MARK)
return NULL;
sysprof_elf_symbol_resolver_resolve_full (SYSPROF_ELF_SYMBOL_RESOLVER (resolver),
time,
pid,
context,
address,
&begin,
&end,
&name,
tag);
return g_steal_pointer (&name);
}
static void
symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface)
{
iface->load = sysprof_elf_symbol_resolver_load;
iface->resolve_with_context = sysprof_elf_symbol_resolver_resolve_with_context;
}
SysprofSymbolResolver *
sysprof_elf_symbol_resolver_new (void)
{
return g_object_new (SYSPROF_TYPE_ELF_SYMBOL_RESOLVER, NULL);
}
void
sysprof_elf_symbol_resolver_add_debug_dir (SysprofElfSymbolResolver *self,
const gchar *debug_dir)
{
/* 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";
}