Add function to folly_fibers.py to get the address of a specific fiber from a fiberMa...
[folly.git] / folly / fibers / scripts / gdb.py
1 #!/usr/bin/env python3
2
3
4 import gdb
5 import gdb.printing
6 import gdb.types
7 import gdb.unwinder
8 import gdb.xmethod
9 import collections
10 import itertools
11
12
13 class FiberPrinter:
14     """Print a folly::fibers::Fiber"""
15
16     def __init__(self, val):
17         self.val = val
18
19         state = self.val['state_']
20         d = gdb.types.make_enum_dict(state.type)
21         d = dict((v, k) for k, v in d.items())
22         self.state = d[int(state)]
23
24     def state_to_string(self):
25         if self.state == "folly::fibers::Fiber::INVALID":
26             return "Invalid"
27         if self.state == "folly::fibers::Fiber::NOT_STARTED":
28             return "Not started"
29         if self.state == "folly::fibers::Fiber::READY_TO_RUN":
30             return "Ready to run"
31         if self.state == "folly::fibers::Fiber::RUNNING":
32             return "Running"
33         if self.state == "folly::fibers::Fiber::AWAITING":
34             return "Awaiting"
35         if self.state == "folly::fibers::Fiber::AWAITING_IMMEDIATE":
36             return "Awaiting immediate"
37         if self.state == "folly::fibers::Fiber::YIELDED":
38             return "Yielded"
39         return "Unknown"
40
41     def backtrace_available(self):
42         return self.state != "folly::fibers::Fiber::INVALID" and \
43             self.state != "folly::fibers::Fiber::NOT_STARTED" and \
44             self.state != "folly::fibers::Fiber::RUNNING"
45
46     def children(self):
47         result = collections.OrderedDict()
48         result["state"] = self.state_to_string()
49         result["backtrace available"] = self.backtrace_available()
50         return result.items()
51
52     def to_string(self):
53         return "folly::fibers::Fiber"
54
55     def display_hint(self):
56         return "folly::fibers::Fiber"
57
58
59 class GetFiberXMethodWorker(gdb.xmethod.XMethodWorker):
60     def get_arg_types(self):
61         return gdb.lookup_type('int')
62
63     def get_result_type(self):
64         return gdb.lookup_type('int')
65
66     def __call__(self, *args):
67         fm = args[0]
68         index = int(args[1])
69         fiber = next(itertools.islice(fiber_manager_active_fibers(fm),
70                                       index,
71                                       None))
72         if fiber is None:
73             raise gdb.GdbError("Index out of range")
74         else:
75             return fiber
76
77
78 class GetFiberXMethodMatcher(gdb.xmethod.XMethodMatcher):
79     def __init__(self):
80         super(GetFiberXMethodMatcher, self).__init__("Fiber address method matcher")
81         self.worker = GetFiberXMethodWorker()
82
83     def match(self, class_type, method_name):
84         if class_type.name == "folly::fibers::FiberManager" and \
85            method_name == "get_fiber":
86             return self.worker
87         return None
88
89
90 def fiber_manager_active_fibers(fm):
91     all_fibers = \
92         fm['allFibers_']['data_']['root_plus_size_']['m_header']
93     fiber_hook = all_fibers['next_']
94
95     fiber_count = 0
96
97     while fiber_hook != all_fibers.address:
98         fiber = fiber_hook.cast(gdb.lookup_type("int64_t"))
99         fiber = fiber - gdb.parse_and_eval(
100             "(int64_t)&'folly::fibers::Fiber'::globalListHook_")
101         fiber = fiber.cast(
102             gdb.lookup_type('folly::fibers::Fiber').pointer()).dereference()
103
104         if FiberPrinter(fiber).state != "folly::fibers::Fiber::INVALID":
105             yield fiber
106
107         fiber_hook = fiber_hook.dereference()['next_']
108
109         fiber_count = fiber_count + 1
110
111
112 class FiberManagerPrinter:
113     """Print a folly::fibers::Fiber"""
114
115     fiber_print_limit = 100
116
117     def __init__(self, fm):
118         self.fm = fm
119
120     def children(self):
121         def limit_with_dots(fibers_iterator):
122             num_items = 0
123             for fiber in fibers_iterator:
124                 if num_items >= self.fiber_print_limit:
125                     yield ('...', '...')
126                     return
127
128                 yield (str(fiber.address), fiber)
129                 num_items += 1
130
131         return limit_with_dots(fiber_manager_active_fibers(self.fm))
132
133     def to_string(self):
134         return "folly::fibers::FiberManager"
135
136     def display_hint(self):
137         return "folly::fibers::FiberManager"
138
139
140 class FiberPrintLimitCommand(gdb.Command):
141     def __init__(self):
142         super(FiberPrintLimitCommand, self).__init__(
143             "fiber-print-limit", gdb.COMMAND_USER)
144
145     def invoke(self, arg, from_tty):
146         if not arg:
147             print("New limit has to be passed to 'fiber_print_limit' command")
148             return
149         FiberManagerPrinter.fiber_print_limit = int(arg)
150         print("New fiber limit for FiberManager printer set to " +
151               str(FiberManagerPrinter.fiber_print_limit))
152
153
154 class FrameId(object):
155     def __init__(self, sp, pc):
156         self.sp = sp
157         self.pc = pc
158
159
160 class FiberUnwinderFrameFilter:
161     instance = None
162
163     @classmethod
164     def set_skip_frame_sp(cls, skip_frame_sp):
165         if cls.instance is None:
166             cls.instance = FiberUnwinderFrameFilter()
167
168         cls.instance.skip_frame_sp = skip_frame_sp
169
170     def __init__(self):
171         self.name = "FiberUnwinderFrameFilter"
172         self.priority = 100
173         self.enabled = True
174         gdb.frame_filters[self.name] = self
175
176     def filter(self, frame_iter):
177         if not self.skip_frame_sp:
178             return frame_iter
179
180         return self.filter_impl(frame_iter)
181
182     def filter_impl(self, frame_iter):
183         for frame in frame_iter:
184             frame_sp = frame.inferior_frame().read_register("rsp")
185             if frame_sp == self.skip_frame_sp:
186                 continue
187             yield frame
188
189
190 class FiberUnwinder(gdb.unwinder.Unwinder):
191     instance = None
192
193     @classmethod
194     def set_fiber(cls, fiber):
195         if cls.instance is None:
196             cls.instance = FiberUnwinder()
197             gdb.unwinder.register_unwinder(None, cls.instance)
198
199         fiber_impl = fiber['fiberImpl_']
200         cls.instance.fiber_context_ptr = fiber_impl['fiberContext_']
201
202     def __init__(self):
203         super(FiberUnwinder, self).__init__("Fiber unwinder")
204         self.fiber_context_ptr = None
205
206     def __call__(self, pending_frame):
207         if not self.fiber_context_ptr:
208             return None
209
210         orig_sp = pending_frame.read_register('rsp')
211         orig_pc = pending_frame.read_register('rip')
212
213         void_star_star = gdb.lookup_type('uint64_t').pointer()
214         ptr = self.fiber_context_ptr.cast(void_star_star)
215
216         # This code may need to be adjusted to newer versions of boost::context.
217         #
218         # The easiest way to get these offsets is to first attach to any
219         # program which uses folly::fibers and add a break point in
220         # boost::context::jump_fcontext. You then need to save information about
221         # frame 1 via 'info frame 1' command.
222         #
223         # After that you need to resume program until fiber switch is complete
224         # and expore the contents of saved fiber context via
225         # 'x/16gx {fiber pointer}->fiberImpl_.fiberContext_' command.
226         # You then need to match those to the following values you've previously
227         # observed in the output of 'info frame 1'  command.
228         #
229         # Value found at "rbp at X" of 'info frame 1' output:
230         rbp = (ptr + 6).dereference()
231         # Value found at "rip = X" of 'info frame 1' output:
232         rip = (ptr + 7).dereference()
233         # Value found at "caller of frame at X" of 'info frame 1' output:
234         rsp = rbp - 96
235
236         frame_id = FrameId(rsp, orig_pc)
237         unwind_info = pending_frame.create_unwind_info(frame_id)
238         unwind_info.add_saved_register('rbp', rbp)
239         unwind_info.add_saved_register('rsp', rsp)
240         unwind_info.add_saved_register('rip', rip)
241
242         self.fiber_context_ptr = None
243
244         FiberUnwinderFrameFilter.set_skip_frame_sp(orig_sp)
245
246         return unwind_info
247
248
249 def fiber_activate(fiber_ptr):
250     fiber_type = gdb.lookup_type("folly::fibers::Fiber")
251     fiber = fiber_ptr.cast(fiber_type.pointer()).dereference()
252     if not FiberPrinter(fiber).backtrace_available():
253         return "Can not activate a non-waiting fiber."
254     FiberUnwinder.set_fiber(fiber)
255     return "Fiber " + str(fiber_ptr) + " activated. You can call 'bt' now."
256
257
258 def fiber_deactivate():
259     FiberUnwinderFrameFilter.set_skip_frame_sp(None)
260     gdb.invalidate_cached_frames()
261     return "Fiber de-activated."
262
263
264 class FiberActivateCommand(gdb.Command):
265     def __init__(self):
266         super(FiberActivateCommand, self).__init__("fiber", gdb.COMMAND_USER)
267
268     def invoke(self, arg, from_tty):
269         if not arg:
270             print("folly::fibers::Fiber* has to be passed to 'fiber' command")
271             return
272         fiber_ptr = gdb.parse_and_eval(arg)
273         print(fiber_activate(fiber_ptr))
274
275
276 class FiberDeactivateCommand(gdb.Command):
277     def __init__(self):
278         super(FiberDeactivateCommand, self).__init__(
279             "fiber-deactivate", gdb.COMMAND_USER)
280
281     def invoke(self, arg, from_tty):
282         print(fiber_deactivate())
283
284
285 class FiberXMethodWorker(gdb.xmethod.XMethodWorker):
286     def get_arg_types(self):
287         return None
288
289     def get_result_type(self):
290         return None
291
292     def __call__(self, *args):
293         return fiber_activate(args[0])
294
295
296 class FiberXMethodMatcher(gdb.xmethod.XMethodMatcher):
297     def __init__(self):
298         super(FiberXMethodMatcher, self).__init__("Fiber method matcher")
299         self.worker = FiberXMethodWorker()
300
301     def match(self, class_type, method_name):
302         if class_type.name == "folly::fibers::Fiber" and \
303            method_name == "activate":
304             return self.worker
305         return None
306
307
308 class Shortcut(gdb.Function):
309     def __init__(self, function_name, value_lambda):
310         super(Shortcut, self).__init__(function_name)
311         self.value_lambda = value_lambda
312
313     def invoke(self):
314         return self.value_lambda()
315
316
317 def get_fiber_manager_map(evb_type):
318     try:
319         # Exception thrown if unable to find type
320         # Probably because of missing debug symbols
321         global_cache_type = gdb.lookup_type(
322             "folly::fibers::(anonymous namespace)::GlobalCache<" + evb_type + ">")
323     except gdb.error:
324         raise gdb.GdbError("Unable to find types. "
325                            "Please make sure debug info is available for this binary.\n"
326                            "Have you run 'fbload debuginfo_fbpkg'?")
327
328     global_cache_instance_ptr_ptr = gdb.parse_and_eval(
329         "&'" + global_cache_type.name + "::instance()::ret'")
330     global_cache_instance_ptr = global_cache_instance_ptr_ptr.cast(
331         global_cache_type.pointer().pointer()).dereference()
332     if global_cache_instance_ptr == 0x0:
333         raise gdb.GdbError("FiberManager map is empty.")
334
335     global_cache_instance = global_cache_instance_ptr.dereference()
336     return global_cache_instance['map_']
337
338
339 def get_fiber_manager_map_evb():
340     return get_fiber_manager_map("folly::EventBase")
341
342
343 def get_fiber_manager_map_vevb():
344     return get_fiber_manager_map("folly::VirtualEventBase")
345
346
347 def build_pretty_printer():
348     pp = gdb.printing.RegexpCollectionPrettyPrinter("folly_fibers")
349     pp.add_printer('fibers::Fiber', '^folly::fibers::Fiber$', FiberPrinter)
350     pp.add_printer('fibers::FiberManager', '^folly::fibers::FiberManager$',
351                    FiberManagerPrinter)
352     return pp
353
354
355 def load():
356     gdb.printing.register_pretty_printer(gdb, build_pretty_printer())
357     gdb.xmethod.register_xmethod_matcher(gdb, FiberXMethodMatcher())
358     gdb.xmethod.register_xmethod_matcher(gdb, GetFiberXMethodMatcher())
359     FiberPrintLimitCommand()
360     FiberActivateCommand()
361     FiberDeactivateCommand()
362     Shortcut("get_fiber_manager_map_evb", get_fiber_manager_map_evb)
363     Shortcut("get_fiber_manager_map_vevb", get_fiber_manager_map_vevb)
364
365
366 def info():
367     return "Pretty printers for folly::fibers"