16
16
from undodb .debugger_extensions .debugger_io import redirect_to_launcher_output
17
17
18
18
19
- def leak_check () -> None :
19
+ def leak_check () -> int :
20
20
"""
21
21
Implements breakpoints and stops on all calls to malloc() and free(), capturing the
22
22
timestamp, size and returned pointer for malloc(), then confirms the address pointer is later
23
23
seen in a free() call.
24
24
25
25
If a subsequent free() is not seen, then at the end of execution, output the timestamp and
26
26
details of the memory which was never freed.
27
+
28
+ Returns the number of unmatched allocations found.
27
29
"""
28
30
# Set a breakpoint for the specified function.
29
31
gdb .Breakpoint ("malloc" )
@@ -53,38 +55,39 @@ def leak_check() -> None:
53
55
54
56
# For now, capture the timestamp and size of memory requested.
55
57
time = udb .time .get ()
56
- size = gdb .parse_and_eval ("$rdi" )
58
+ size = int ( gdb .parse_and_eval ("$rdi" ) )
57
59
58
60
gdb .execute ("continue" )
59
61
60
62
# Should stop at the finish breakpoint, so capture the pointer.
61
- addr = mfbp .return_value
63
+ assert mfbp .return_value is not None , "Expected to see a return value."
64
+ addr = int (mfbp .return_value )
62
65
63
66
if addr :
64
67
# Store details in the dictionary.
65
- allocations [format (addr )] = time , size
68
+ allocations [hex (addr )] = time , size
66
69
else :
67
70
print (f"-- INFO: Malloc called for { size } byte(s) but null returned." )
68
71
69
72
print (f"{ time } : malloc() called: { size } byte(s) allocated at { addr } ." )
70
73
71
74
elif re .search ("free" , mypc ):
72
75
# In free(), get the pointer address.
73
- addr = gdb .parse_and_eval ("$rdi" )
76
+ addr = int ( gdb .parse_and_eval ("$rdi" ) )
74
77
75
78
time = udb .time .get ()
76
79
77
80
# Delete entry from the dictionary as this memory was released.
78
81
if addr > 0 :
79
- if allocations [hex (int ( format ( addr )) )]:
80
- del allocations [hex (int ( format ( addr )) )]
82
+ if allocations [hex (addr )]:
83
+ del allocations [hex (addr )]
81
84
else :
82
85
print ("--- INFO: Free called with unknown address" )
83
86
else :
84
87
print ("--- INFO: Free called with null address" )
85
88
86
89
# with redirect_to_launcher_output():
87
- print (f"{ time } : free() called for { int ( addr ) :#x} " )
90
+ print (f"{ time } : free() called for { addr :#x} " )
88
91
89
92
# If Allocations has any entries remaining, they were not released.
90
93
with redirect_to_launcher_output ():
@@ -96,11 +99,10 @@ def leak_check() -> None:
96
99
97
100
# Increase the amount of source from default (10) to 16 lines for more context.
98
101
gdb .execute ("set listsize 16" )
99
- for addr in allocations :
100
- time , size = allocations [addr ]
102
+ for location , (time , size ) in allocations .items ():
101
103
total += size
102
104
print ("===============================================================================" )
103
- print (f"{ time } : { size } bytes was allocated at { addr } , but never freed." )
105
+ print (f"{ time } : { size } bytes was allocated at { location } , but never freed." )
104
106
print ("===============================================================================" )
105
107
udb .time .goto (time )
106
108
print ("Backtrace:" )
@@ -124,10 +126,10 @@ def leak_check() -> None:
124
126
# UDB will automatically load the modules passed to UdbLauncher.add_extension and, if present,
125
127
# automatically execute any function (with no arguments) called "run".
126
128
def run () -> None :
127
- # Needed to allow GDB to fixup breakpoints properly after glibc has been loaded
129
+ # Needed to allow GDB to fixup breakpoints properly after glibc has been loaded.
128
130
gdb .Breakpoint ("main" )
129
131
130
132
unmatched = leak_check ()
131
133
132
- # Pass the number of time we hit the breakpoint back to the outer script.
134
+ # Pass the number of unmatched allocations back to the outer script.
133
135
udb .result_data ["unmatched" ] = unmatched
0 commit comments