1# Copyright (C) 2021-2023 Free Software Foundation, Inc. 2 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 3 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16import tracemalloc 17import gdb 18import re 19 20# A global variable in which we store a reference to the gdb.Inferior 21# object sent to us in the new_inferior event. 22inf = None 23 24# Register the new_inferior event handler. 25def new_inferior_handler(event): 26 global inf 27 inf = event.inferior 28 29 30gdb.events.new_inferior.connect(new_inferior_handler) 31 32# A global filters list, we only care about memory allocations 33# originating from this script. 34filters = [tracemalloc.Filter(True, "*py-inferior-leak.py")] 35 36# Add a new inferior, and return the number of the new inferior. 37def add_inferior(): 38 output = gdb.execute("add-inferior", False, True) 39 m = re.search(r"Added inferior (\d+)", output) 40 if m: 41 num = int(m.group(1)) 42 else: 43 raise RuntimeError("no match") 44 return num 45 46 47# Run the test. When CLEAR is True we clear the global INF variable 48# before comparing the before and after memory allocation traces. 49# When CLEAR is False we leave INF set to reference the gdb.Inferior 50# object, thus preventing the gdb.Inferior from being deallocated. 51def test(clear): 52 global filters, inf 53 54 # Start tracing, and take a snapshot of the current allocations. 55 tracemalloc.start() 56 snapshot1 = tracemalloc.take_snapshot() 57 58 # Create an inferior, this triggers the new_inferior event, which 59 # in turn holds a reference to the new gdb.Inferior object in the 60 # global INF variable. 61 num = add_inferior() 62 gdb.execute("remove-inferiors %s" % num) 63 64 # Possibly clear the global INF variable. 65 if clear: 66 inf = None 67 68 # Now grab a second snapshot of memory allocations, and stop 69 # tracing memory allocations. 70 snapshot2 = tracemalloc.take_snapshot() 71 tracemalloc.stop() 72 73 # Filter the snapshots; we only care about allocations originating 74 # from this file. 75 snapshot1 = snapshot1.filter_traces(filters) 76 snapshot2 = snapshot2.filter_traces(filters) 77 78 # Compare the snapshots, this leaves only things that were 79 # allocated, but not deallocated since the first snapshot. 80 stats = snapshot2.compare_to(snapshot1, "traceback") 81 82 # Total up all the deallocated things. 83 total = 0 84 for stat in stats: 85 total += stat.size_diff 86 return total 87 88 89# The first time we run this some global state will be allocated which 90# shows up as memory that is allocated, but not released. So, run the 91# test once and discard the result. 92test(True) 93 94# Now run the test twice, the first time we clear our global reference 95# to the gdb.Inferior object, which should allow Python to deallocate 96# the object. The second time we hold onto the global reference, 97# preventing Python from performing the deallocation. 98bytes_with_clear = test(True) 99bytes_without_clear = test(False) 100 101# The bug that used to exist in GDB was that even when we released the 102# global reference the gdb.Inferior object would not be deallocated. 103if bytes_with_clear > 0: 104 raise gdb.GdbError("memory leak when gdb.Inferior should be released") 105if bytes_without_clear == 0: 106 raise gdb.GdbError("gdb.Inferior object is no longer allocated") 107 108# Print a PASS message that the test script can see. 109print("PASS") 110