2 # -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
4 # This file is part of the LibreOffice project.
6 # This Source Code Form is subject to the terms of the Mozilla Public
7 # License, v. 2.0. If a copy of the MPL was not distributed with this
8 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 ### script to help debug leaks of reference counted objects
13 ## I. to use it, first override acquire() and release()
15 # Foo * g_pTrackedFoo = 0;
18 # static int nFoos = 0;
19 # if (++nFoos == 42) // track instance #42
20 # g_pTrackedFoo = this;
23 # if (this == g_pTrackedFoo)
24 # ; // set gdb breakpoint here
28 # if (this == g_pTrackedFoo)
29 # ; // set gdb breakpoint here
32 ## II. run test/soffice in gdb and set breakpoints in acquire/release
33 ## with a command to print the backtrace
35 # set logging enabled on
45 ## III. now feed logfile gdb.txt into this script
47 # bin/refcount_leak.py < gdb.txt
51 from operator
import itemgetter
58 clock
= 0 # global counter
59 # frames: list of stack frames, beginning with outermost
60 def __init__(self
, lines
):
64 self
.clock
= Trace
.clock
66 def addTrace(traces
, lines
):
67 if not(traces
is None) and len(lines
) > 0:
68 traces
.append(Trace(lines
))
70 def readGdbLog(infile
):
75 apattern
= re
.compile("^Breakpoint.*::acquire")
76 rpattern
= re
.compile("^Breakpoint.*::release")
78 if apattern
.match(line
):
79 addTrace(current
, lines
)
81 current
= traces_acquire
82 if rpattern
.match(line
):
83 addTrace(current
, lines
)
85 current
= traces_release
86 if line
.startswith("#"):
87 # strip #123 stack frame number, and newline
88 lines
.append(line
[line
.index("0x"):-1])
89 addTrace(current
, lines
)
90 print("# parsed traces acquire: ", len(traces_acquire
))
91 print("# parsed traces release: ", len(traces_release
))
92 return (traces_acquire
, traces_release
)
94 def getFunction(frame
):
95 start
= frame
.index(" in ") + len(" in ")
97 end
= frame
.index(" at ", start
)
98 except ValueError as e
:
99 # argh... stack frames may be split across multiple lines if
100 # a parameter has a fancy pretty printer
102 return frame
[start
:end
]
105 def matchStack(trace_acquire
, trace_release
):
106 if trace_release
.clock
< trace_acquire
.clock
:
107 return None # acquire must precede release
109 refpattern
= re
.compile(r
"::Reference<.*>::Reference\(")
110 for (frame1
, frame2
) in zip(trace_release
.frames
, trace_acquire
.frames
):
114 if getFunction(frame1
) == getFunction(frame2
):
116 acquireframes
= len(trace_acquire
.frames
)
117 # there is sometimes a dozen frames of UNO type related junk
118 # on the stack where the acquire() happens, which breaks the
119 # matching; try to avoid that
120 for i
in range(common
, acquireframes
):
121 if refpattern
.search(trace_acquire
.frames
[i
]):
122 acquireframes
= i
+1 # cut off junk above Reference ctor
124 score
= max(len(trace_release
.frames
), acquireframes
) - common
125 # smaller score is better
126 return (score
, trace_release
.clock
- trace_acquire
.clock
)
128 # brute force greedy n^2 matching
129 def matchStacks(traces_acquire
, traces_release
):
131 for release
in traces_release
:
132 for acquire
in traces_acquire
:
133 score
= matchStack(acquire
, release
)
134 if score
is not None:
135 matches
.append((score
, acquire
, release
))
136 matches
.sort(key
=itemgetter(0))
139 def bestMatches(traces_acquire
, traces_release
, matches
):
140 traces_aunmatched
= traces_acquire
141 traces_runmatched
= traces_release
143 for (score
,acquire
,release
) in matches
:
144 if not(acquire
in traces_aunmatched
and release
in traces_runmatched
):
146 traces_aunmatched
.remove(acquire
)
147 traces_runmatched
.remove(release
)
148 bestmatches
.append((score
,acquire
,release
))
149 print("# unmatched acquire: ", len(traces_aunmatched
))
150 print("# unmatched release: ", len(traces_runmatched
))
151 return (bestmatches
,traces_aunmatched
,traces_runmatched
)
153 def printTrace(trace
):
154 for frame
in reversed(trace
.frames
):
157 def printMatched(bestmatches
):
158 for (score
,acquire
,release
) in reversed(bestmatches
):
159 print("\n*** Matched trace with score: ", score
)
165 def printUnmatched(traces
, prefix
):
167 print("\n*** Unmatched trace (", prefix
, "):")
170 if __name__
== "__main__":
171 (traces_acquire
, traces_release
) = readGdbLog(sys
.stdin
)
172 matches
= matchStacks(traces_acquire
, traces_release
)
173 (bestmatches
,traces_au
,traces_ru
) = bestMatches(traces_acquire
, traces_release
, matches
)
174 # print output, sorted with the most suspicious stuff first:
175 printUnmatched(traces_au
, "acquire")
176 printUnmatched(traces_ru
, "release")
177 printMatched(bestmatches
)
179 # vim:set shiftwidth=4 softtabstop=4 expandtab: