]>
Commit | Line | Data |
---|---|---|
1d506c26 | 1 | # Copyright (C) 2021-2024 Free Software Foundation, Inc. |
cb6e6bb8 AB |
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 | ||
16 | import tracemalloc | |
17 | import gdb | |
18 | import 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. | |
22 | inf = None | |
23 | ||
09de95fb | 24 | |
cb6e6bb8 AB |
25 | # Register the new_inferior event handler. |
26 | def new_inferior_handler(event): | |
27 | global inf | |
28 | inf = event.inferior | |
29 | ||
30 | ||
31 | gdb.events.new_inferior.connect(new_inferior_handler) | |
32 | ||
33 | # A global filters list, we only care about memory allocations | |
34 | # originating from this script. | |
35 | filters = [tracemalloc.Filter(True, "*py-inferior-leak.py")] | |
36 | ||
09de95fb | 37 | |
cb6e6bb8 AB |
38 | # Add a new inferior, and return the number of the new inferior. |
39 | def add_inferior(): | |
40 | output = gdb.execute("add-inferior", False, True) | |
41 | m = re.search(r"Added inferior (\d+)", output) | |
42 | if m: | |
43 | num = int(m.group(1)) | |
44 | else: | |
45 | raise RuntimeError("no match") | |
46 | return num | |
47 | ||
48 | ||
49 | # Run the test. When CLEAR is True we clear the global INF variable | |
50 | # before comparing the before and after memory allocation traces. | |
51 | # When CLEAR is False we leave INF set to reference the gdb.Inferior | |
52 | # object, thus preventing the gdb.Inferior from being deallocated. | |
53 | def test(clear): | |
54 | global filters, inf | |
55 | ||
56 | # Start tracing, and take a snapshot of the current allocations. | |
57 | tracemalloc.start() | |
58 | snapshot1 = tracemalloc.take_snapshot() | |
59 | ||
60 | # Create an inferior, this triggers the new_inferior event, which | |
61 | # in turn holds a reference to the new gdb.Inferior object in the | |
62 | # global INF variable. | |
63 | num = add_inferior() | |
64 | gdb.execute("remove-inferiors %s" % num) | |
65 | ||
66 | # Possibly clear the global INF variable. | |
67 | if clear: | |
68 | inf = None | |
69 | ||
70 | # Now grab a second snapshot of memory allocations, and stop | |
71 | # tracing memory allocations. | |
72 | snapshot2 = tracemalloc.take_snapshot() | |
73 | tracemalloc.stop() | |
74 | ||
75 | # Filter the snapshots; we only care about allocations originating | |
76 | # from this file. | |
77 | snapshot1 = snapshot1.filter_traces(filters) | |
78 | snapshot2 = snapshot2.filter_traces(filters) | |
79 | ||
80 | # Compare the snapshots, this leaves only things that were | |
81 | # allocated, but not deallocated since the first snapshot. | |
82 | stats = snapshot2.compare_to(snapshot1, "traceback") | |
83 | ||
84 | # Total up all the deallocated things. | |
85 | total = 0 | |
86 | for stat in stats: | |
87 | total += stat.size_diff | |
88 | return total | |
89 | ||
90 | ||
91 | # The first time we run this some global state will be allocated which | |
92 | # shows up as memory that is allocated, but not released. So, run the | |
93 | # test once and discard the result. | |
94 | test(True) | |
95 | ||
96 | # Now run the test twice, the first time we clear our global reference | |
97 | # to the gdb.Inferior object, which should allow Python to deallocate | |
98 | # the object. The second time we hold onto the global reference, | |
99 | # preventing Python from performing the deallocation. | |
100 | bytes_with_clear = test(True) | |
101 | bytes_without_clear = test(False) | |
102 | ||
103 | # The bug that used to exist in GDB was that even when we released the | |
104 | # global reference the gdb.Inferior object would not be deallocated. | |
105 | if bytes_with_clear > 0: | |
106 | raise gdb.GdbError("memory leak when gdb.Inferior should be released") | |
107 | if bytes_without_clear == 0: | |
108 | raise gdb.GdbError("gdb.Inferior object is no longer allocated") | |
109 | ||
110 | # Print a PASS message that the test script can see. | |
111 | print("PASS") |