From f7a32750e2298893922da110b74176828df68ff1 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 14 Aug 2023 15:56:46 -0700 Subject: [PATCH] libsysprof: add leak detector helper This is a leak detector by finding allocations which do not have a corresponding free record. --- src/libsysprof/meson.build | 1 + .../sysprof-leak-detector-private.h | 32 ++++ src/libsysprof/sysprof-leak-detector.c | 157 ++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 src/libsysprof/sysprof-leak-detector-private.h create mode 100644 src/libsysprof/sysprof-leak-detector.c diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index ffb8c2f7..b91f156b 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -135,6 +135,7 @@ libsysprof_private_sources = [ 'sysprof-document-symbols.c', 'sysprof-elf-loader.c', 'sysprof-elf.c', + 'sysprof-leak-detector.c', 'sysprof-maps-parser.c', 'sysprof-mount-device.c', 'sysprof-mount-namespace.c', diff --git a/src/libsysprof/sysprof-leak-detector-private.h b/src/libsysprof/sysprof-leak-detector-private.h new file mode 100644 index 00000000..abcd5d12 --- /dev/null +++ b/src/libsysprof/sysprof-leak-detector-private.h @@ -0,0 +1,32 @@ +/* sysprof-leak-detector-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "eggbitset.h" + +#include "sysprof-document.h" + +G_BEGIN_DECLS + +EggBitset *sysprof_leak_detector_detect (SysprofDocument *document, + EggBitset *allocations); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-leak-detector.c b/src/libsysprof/sysprof-leak-detector.c new file mode 100644 index 00000000..b827dcf5 --- /dev/null +++ b/src/libsysprof/sysprof-leak-detector.c @@ -0,0 +1,157 @@ +/* sysprof-leak-detector.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-allocation.h" +#include "sysprof-document-frame-private.h" +#include "sysprof-document-private.h" +#include "sysprof-leak-detector-private.h" + +typedef struct _SysprofAllocNode SysprofAllocNode; + +#include "tree.h" + +struct _SysprofAllocNode +{ + RB_ENTRY(_SysprofAllocNode) link; + guint64 address; + gint64 size; + guint pos; +}; + +static int +sysprof_alloc_node_compare (SysprofAllocNode *a, + SysprofAllocNode *b) +{ + if (a->address < b->address) + return -1; + else if (a->address > b->address) + return 1; + else + return 0; +} + +typedef struct _SysprofAllocs +{ + RB_HEAD(sysprof_allocs, _SysprofAllocNode) head; +} SysprofAllocs; + +RB_GENERATE_STATIC(sysprof_allocs, _SysprofAllocNode, link, sysprof_alloc_node_compare); + +static void +sysprof_alloc_node_free (SysprofAllocNode *node) +{ + SysprofAllocNode *right = RB_RIGHT(node, link); + SysprofAllocNode *left = RB_LEFT(node, link); + + g_free (left); + g_free (right); + g_free (node); +} + +EggBitset * +sysprof_leak_detector_detect (SysprofDocument *document, + EggBitset *allocations) +{ + SysprofAllocs allocs; + g_autoptr(SysprofDocumentFrame) frame = NULL; + g_autoptr(EggBitset) leaks = NULL; + g_autoptr(GArray) frames = NULL; + const char *base_addr; + EggBitsetIter iter; + SysprofAllocNode find = {0}; + SysprofAllocNode *res; + guint pos; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (document), NULL); + g_return_val_if_fail (allocations != NULL, NULL); + + leaks = egg_bitset_new_empty (); + + if (!egg_bitset_iter_init_first (&iter, allocations, &pos)) + return g_steal_pointer (&leaks); + + frames = _sysprof_document_get_frames (document); + + /* Load the first allocation to look at. To avoid creating lots + * of GObjects, we will update the internal data in the alloc + * object to point at each subsequence record. That way it's just + * used as a demarshalling container. + */ + frame = g_list_model_get_item (G_LIST_MODEL (document), pos); + frame->time_offset = 0; + + g_assert (frame != NULL); + g_assert (SYSPROF_IS_DOCUMENT_ALLOCATION (frame)); + + base_addr = g_mapped_file_get_contents (frame->mapped_file); + + RB_INIT (&allocs.head); + + for (;;) + { + SysprofDocumentAllocation *alloc = (SysprofDocumentAllocation *)frame; + SysprofDocumentFramePointer *ptr = &g_array_index (frames, SysprofDocumentFramePointer, pos); + guint64 address = sysprof_document_allocation_get_address (alloc); + gint64 size = sysprof_document_allocation_get_size (alloc); + + /* Generally we'd want to take into account the PID as well, but + * since we only support recording from LD_PRELOAD currently, there + * isn't much of a chance of someone muxing multiple records into + * a single capture. Therefore, don't waste the time on it. + */ + + if (size > 0) + { + SysprofAllocNode *node = g_new0 (SysprofAllocNode, 1); + + node->pos = pos; + node->address = address; + node->size = size; + + RB_INSERT (sysprof_allocs, &allocs.head, node); + } + else + { + find.address = address; + + if ((res = RB_FIND (sysprof_allocs, &allocs.head, &find))) + RB_REMOVE (sysprof_allocs, &allocs.head, res); + } + + if (!egg_bitset_iter_next (&iter, &pos)) + break; + + frame->frame = (SysprofCaptureFrame *)&base_addr[ptr->offset]; + frame->frame_len = ptr->length; + } + + RB_FOREACH (res, sysprof_allocs, &allocs.head) { + g_assert (res->size > 0); + + egg_bitset_add (leaks, res->pos); + } + + if (RB_ROOT (&allocs.head)) + sysprof_alloc_node_free (RB_ROOT (&allocs.head)); + + return g_steal_pointer (&leaks); +}