An interpreted, interactive, object-oriented programming language
CentOS Sources
2017-08-01 71084d584ff953f5463757ec6536406320560b4d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
--- Tools/gdb/libpython.py.orig    2013-10-09 10:54:59.894701668 +0200
+++ Tools/gdb/libpython.py    2013-10-09 11:09:30.278703290 +0200
@@ -1194,39 +1194,113 @@
             iter_frame = iter_frame.newer()
         return index
 
+    # We divide frames into:
+    #   - "python frames":
+    #       - "bytecode frames" i.e. PyEval_EvalFrameEx
+    #       - "other python frames": things that are of interest from a python
+    #         POV, but aren't bytecode (e.g. GC, GIL)
+    #   - everything else
+
+    def is_python_frame(self):
+        '''Is this a PyEval_EvalFrameEx frame, or some other important
+        frame? (see is_other_python_frame for what "important" means in this
+        context)'''
+        if self.is_evalframeex():
+            return True
+        if self.is_other_python_frame():
+            return True
+        return False
+
     def is_evalframeex(self):
-        '''Is this a PyEval_EvalFrameEx frame?'''
-        if self._gdbframe.name() == 'PyEval_EvalFrameEx':
-            '''
-            I believe we also need to filter on the inline
-            struct frame_id.inline_depth, only regarding frames with
-            an inline depth of 0 as actually being this function
-
-            So we reject those with type gdb.INLINE_FRAME
-            '''
-            if self._gdbframe.type() == gdb.NORMAL_FRAME:
-                # We have a PyEval_EvalFrameEx frame:
-                return True
+        if self._gdbframe.function():
+            if self._gdbframe.function().name == 'PyEval_EvalFrameEx':
+                '''
+                I believe we also need to filter on the inline
+                struct frame_id.inline_depth, only regarding frames with
+                an inline depth of 0 as actually being this function
+
+                So we reject those with type gdb.INLINE_FRAME
+                '''
+                if self._gdbframe.type() == gdb.NORMAL_FRAME:
+                    # We have a PyEval_EvalFrameEx frame:
+                    return True
+
+        return False
+
+    def is_other_python_frame(self):
+        '''Is this frame worth displaying in python backtraces?
+        Examples:
+          - waiting on the GIL
+          - garbage-collecting
+          - within a CFunction
+         If it is, return a descriptive string
+         For other frames, return False
+         '''
+        if self.is_waiting_for_gil():
+            return 'Waiting for a lock (e.g. GIL)'
+        elif self.is_gc_collect():
+            return 'Garbage-collecting'
+        else:
+            # Detect invocations of PyCFunction instances:
+            if self._gdbframe.name() == 'PyCFunction_Call':
+                try:
+                    func = self._gdbframe.read_var('func')
+                    # Use the prettyprinter for the func:
+                    return str(func)
+                except RuntimeError:
+                    return 'PyCFunction invocation (unable to read "func")'
+            older = self.older()
+            if older and older._gdbframe.name() == 'call_function':
+                # Within that frame:
+                # 'call_function' contains, amongst other things, a
+                # hand-inlined copy of PyCFunction_Call.
+                #   "func" is the local containing the PyObject* of the
+                # callable instance
+                # Report it, but only if it's a PyCFunction (since otherwise
+                # we'd be reporting an implementation detail of every other
+                # function invocation)
+                try:
+                    func = older._gdbframe.read_var('func')
+                    funcobj = PyObjectPtr.from_pyobject_ptr(func)
+                    if isinstance(funcobj, PyCFunctionObjectPtr):
+                        # Use the prettyprinter for the func:
+                        return str(func)
+                except RuntimeError:
+                    return False
 
+        # This frame isn't worth reporting:
         return False
 
+    def is_waiting_for_gil(self):
+        '''Is this frame waiting for a lock?'''
+        framename = self._gdbframe.name()
+        if framename:
+            return 'pthread_cond_timedwait' in framename or \
+                   'PyThread_acquire_lock' in framename
+
+    def is_gc_collect(self):
+        '''Is this frame "collect" within the the garbage-collector?'''
+        return self._gdbframe.name() == 'collect'
+
     def get_pyop(self):
         try:
             f = self._gdbframe.read_var('f')
-            frame = PyFrameObjectPtr.from_pyobject_ptr(f)
-            if not frame.is_optimized_out():
-                return frame
-            # gdb is unable to get the "f" argument of PyEval_EvalFrameEx()
-            # because it was "optimized out". Try to get "f" from the frame
-            # of the caller, PyEval_EvalCodeEx().
-            orig_frame = frame
-            caller = self._gdbframe.older()
-            if caller:
-                f = caller.read_var('f')
-                frame = PyFrameObjectPtr.from_pyobject_ptr(f)
-                if not frame.is_optimized_out():
-                    return frame
-            return orig_frame
+            obj = PyFrameObjectPtr.from_pyobject_ptr(f)
+            if isinstance(obj, PyFrameObjectPtr):
+                return obj
+            else:
+                return None
+        except ValueError:
+            return None
+
+    def get_py_co(self):
+        try:
+            co = self._gdbframe.read_var('co')
+            obj = PyCodeObjectPtr.from_pyobject_ptr(co)
+            if isinstance(obj, PyCodeObjectPtr):
+                return obj
+            else:
+                return None
         except ValueError:
             return None
 
@@ -1239,8 +1313,22 @@
 
     @classmethod
     def get_selected_python_frame(cls):
-        '''Try to obtain the Frame for the python code in the selected frame,
-        or None'''
+        '''Try to obtain the Frame for the python-related code in the selected
+        frame, or None'''
+        frame = cls.get_selected_frame()
+
+        while frame:
+            if frame.is_python_frame():
+                return frame
+            frame = frame.older()
+
+        # Not found:
+        return None
+
+    @classmethod
+    def get_selected_bytecode_frame(cls):
+        '''Try to obtain the Frame for the python bytecode interpreter in the
+        selected GDB frame, or None'''
         frame = cls.get_selected_frame()
 
         while frame:
@@ -1265,7 +1353,11 @@
             else:
                 sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
         else:
-            sys.stdout.write('#%i\n' % self.get_index())
+            info = self.is_other_python_frame()
+            if info:
+                sys.stdout.write('#%i %s\n' % (self.get_index(), info))
+            else:
+                sys.stdout.write('#%i\n' % self.get_index())
 
 class PyList(gdb.Command):
     '''List the current Python source code, if any
@@ -1301,7 +1393,7 @@
         if m:
             start, end = map(int, m.groups())
 
-        frame = Frame.get_selected_python_frame()
+        frame = Frame.get_selected_bytecode_frame()
         if not frame:
             print 'Unable to locate python frame'
             return
@@ -1353,7 +1445,7 @@
         if not iter_frame:
             break
 
-        if iter_frame.is_evalframeex():
+        if iter_frame.is_python_frame():
             # Result:
             if iter_frame.select():
                 iter_frame.print_summary()
@@ -1407,7 +1499,7 @@
     def invoke(self, args, from_tty):
         frame = Frame.get_selected_python_frame()
         while frame:
-            if frame.is_evalframeex():
+            if frame.is_python_frame():
                 frame.print_summary()
             frame = frame.older()