# Copyright (C) 2024 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 os import tracemalloc import gdb # A global variable in which we store a reference to the memory buffer # returned from gdb.Inferior.read_memory(). mem_buf = None # A global filters list, we only care about memory allocations # originating from this script. filters = [tracemalloc.Filter(True, "*" + os.path.basename(__file__))] # 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, mem_buf addr = gdb.parse_and_eval("px") inf = gdb.inferiors()[0] # Start tracing, and take a snapshot of the current allocations. tracemalloc.start() snapshot1 = tracemalloc.take_snapshot() # Read from the inferior, this allocate a memory buffer object. mem_buf = inf.read_memory(addr, 4096) # Possibly clear the global INF variable. if clear: mem_buf = 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 allocated 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 memory buffer 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 memory buffer should be released") if bytes_without_clear == 0: raise gdb.GdbError("memory buffer object is no longer allocated") # Print a PASS message that the test script can see. print("PASS")