# Copyright (C) 2021-2023 Free Software Foundation, Inc. # 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 . import tracemalloc import gdb import re # A global variable in which we store a reference to the gdb.Inferior # object sent to us in the new_inferior event. inf = None # Register the new_inferior event handler. def new_inferior_handler(event): global inf inf = event.inferior gdb.events.new_inferior.connect(new_inferior_handler) # A global filters list, we only care about memory allocations # originating from this script. filters = [tracemalloc.Filter(True, "*py-inferior-leak.py")] # Add a new inferior, and return the number of the new inferior. def add_inferior(): output = gdb.execute("add-inferior", False, True) m = re.search(r"Added inferior (\d+)", output) if m: num = int(m.group(1)) else: raise RuntimeError("no match") return num # Run the test. When CLEAR is True we clear the global INF variable # before comparing the before and after memory allocation traces. # When CLEAR is False we leave INF set to reference the gdb.Inferior # object, thus preventing the gdb.Inferior from being deallocated. def test(clear): global filters, inf # Start tracing, and take a snapshot of the current allocations. tracemalloc.start() snapshot1 = tracemalloc.take_snapshot() # Create an inferior, this triggers the new_inferior event, which # in turn holds a reference to the new gdb.Inferior object in the # global INF variable. num = add_inferior() gdb.execute("remove-inferiors %s" % num) # Possibly clear the global INF variable. if clear: inf = None # Now grab a second snapshot of memory allocations, and stop # tracing memory allocations. snapshot2 = tracemalloc.take_snapshot() tracemalloc.stop() # Filter the snapshots; we only care about allocations originating # from this file. snapshot1 = snapshot1.filter_traces(filters) snapshot2 = snapshot2.filter_traces(filters) # Compare the snapshots, this leaves only things that were # allocated, but not deallocated since the first snapshot. stats = snapshot2.compare_to(snapshot1, "traceback") # Total up all the deallocated things. total = 0 for stat in stats: total += stat.size_diff return total # The first time we run this some global state will be allocated which # shows up as memory that is allocated, but not released. So, run the # test once and discard the result. test(True) # Now run the test twice, the first time we clear our global reference # to the gdb.Inferior object, which should allow Python to deallocate # the object. The second time we hold onto the global reference, # preventing Python from performing the deallocation. bytes_with_clear = test(True) bytes_without_clear = test(False) # The bug that used to exist in GDB was that even when we released the # global reference the gdb.Inferior object would not be deallocated. if bytes_with_clear > 0: raise gdb.GdbError("memory leak when gdb.Inferior should be released") if bytes_without_clear == 0: raise gdb.GdbError("gdb.Inferior object is no longer allocated") # Print a PASS message that the test script can see. print("PASS")